啟動Dubbo項目注冊Zookeeper時提示zookeeper not connected異常原理解析


文/朱季謙

遇到一個很詭異的問題,我在啟動多個配置相同zookeeper的Dubbo項目時,其他項目都是正常啟動,唯獨有一個項目在啟動過程中,Dubbo注冊zookeeper協議時,竟然出現了這樣的異常提示——

Caused by: java.lang.IllegalStateException: zookeeper not connected
	at org.apache.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient.<init>(CuratorZookeeperClient.java:80)
	... 79 common frames omitted

我愣了一下,原以為是zookeeper集群掛了,然后檢查了一下,都正常啊,奇怪的是,其他系統也是正常連接,為啥會有一台出現了這樣的異常呢?

看了一下異常提示,當我深入研究了一下出錯的地方時,才恍然明白出現這個異常究竟是為什么了。

可謂是,在源碼面前,一切都是裸泳。

先來看異常提示出現的類方法CuratorZookeeperClient,這個方法的作用是建立zookeeper客戶端的連接,類似http通信一般,在建立通信前,需要先建立三次握手連接,同理,在zookeeper客戶端創建各類節點前,同樣需要先建立客戶端連接到服務器上——

 public CuratorZookeeperClient(URL url) {
        super(url);
        try {
            int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);
            int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);
            CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
                    .connectString(url.getBackupAddress())
                    .retryPolicy(new RetryNTimes(1, 1000))
                    .connectionTimeoutMs(timeout)
                    .sessionTimeoutMs(sessionExpireMs);
            String authority = url.getAuthority();
            if (authority != null && authority.length() > 0) {
                builder = builder.authorization("digest", authority.getBytes());
            }
            client = builder.build();
            client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));
            client.start();
            boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
            if (!connected) {
                throw new IllegalStateException("zookeeper not connected");
            }
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

根據CuratorZookeeperClient方法可知,出現zookeeper not connected異常提示是發生在這一段代碼當中——

if (!connected) {
    throw new IllegalStateException("zookeeper not connected");
}

connected表示連接狀態,當它的值為false時,便會執行這段代碼,那么,究竟是什么情況會導致它的值為false呢?

接下來,讓我們打一個斷點,一步一步解析這段代碼。

首先,用作測試的dubbo和zookeeper配置如下——

dubbo:
  application:
    name: testervice
  registry:
    address: zookeeper://120.77.217.245
#    timeout: 20000
  protocol:
    name: dubbo
    port: 20880

解析來,開始debug,打斷點,CuratorZookeeperClient方法參數url主要包含以下信息——
image
第一步、從url中獲取超時時間timeout參數——

int timeout = url.getParameter(TIMEOUT_KEY, DEFAULT_CONNECTION_TIMEOUT_MS);

這里的大概邏輯是,如果yaml配置registry注冊zookeeper部分參數當中含有 timeout話,那么就返回配置當中定義的超時時間,如果yaml沒有進行配置,那么,就用默認的超時時間,默認即常量DEFAULT_CONNECTION_TIMEOUT_MS,值是5 * 1000,也就是5秒,這個參數其實就是本篇文章的核心。

若自定義形式配置該參數,形式如下timeout: 20000——

dubbo:
  application:
    name: testervice
  registry:
    address: zookeeper://120.77.217.245
    timeout: 20000

第二步、獲取客戶端過期時間——

 int sessionExpireMs = url.getParameter(ZK_SESSION_EXPIRE_KEY, DEFAULT_SESSION_TIMEOUT_MS);

同理,無自定義配置話,則使用默認值DEFAULT_SESSION_TIMEOUT_MS = 60 * 1000,即6分鍾;

第三步、創建一個設置過期時間為6分鍾,連接超時為5秒,重試策略為每秒重試一次,連接服務端為url.getBackupAddress()(注:我這里得到的是120.77.217.245:9090,即配置的zookeeper連接url)的CuratorFramework客戶端實例——

CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
          .connectString(url.getBackupAddress())
          .retryPolicy(new RetryNTimes(1, 1000))
          .connectionTimeoutMs(timeout)
          .sessionTimeoutMs(sessionExpireMs);
client = builder.build();

第四步、添加連接狀態的監控,可以監控操作節點與連接情況——

client.getConnectionStateListenable().addListener(new CuratorConnectionStateListener(url));

第五步、開啟客戶端——

client.start();

最后一步,監控客戶端連接情況,若能連接成功,則證明創建客戶端成功,反之,失敗。可見,若出現zookeeper not connected,問題就在於客戶端連接過程是失敗的,至於為何失敗,原理就在client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS)代碼里。

 boolean connected = client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS);
if (!connected) {
       throw new IllegalStateException("zookeeper not connected");
}

進入到 client.blockUntilConnected(timeout, TimeUnit.MILLISECONDS)源碼里,這里的maxWaitTime即前邊的timeout,默認值是5秒,大概分析一下下邊代碼——

public synchronized boolean blockUntilConnected(int maxWaitTime, TimeUnit units) throws InterruptedException
{
    //獲取當前時間
    long startTime = System.currentTimeMillis();
    //這里是true
    boolean hasMaxWait = (units != null);
    //maxWaitTimeMs等於5000毫秒,即5秒
    long maxWaitTimeMs = hasMaxWait ? TimeUnit.MILLISECONDS.convert(maxWaitTime, units) : 0;
	
    while ( !isConnected() )
    {
        //hasMaxWait為true
        if ( hasMaxWait )
        {   
            //倒數5秒
            long waitTime = maxWaitTimeMs - (System.currentTimeMillis() - startTime);
            //執行到這里,已經過去5秒話,就執行以下方法,返回isConnected()值
            if ( waitTime <= 0 )
            {
                return isConnected();
            }
           //還沒到5秒話,假如執行到這里還有3秒,那么就會執行Object.wait(long timeout)方法,即該線程阻塞3秒后再自動喚醒,接着繼續執行
            wait(waitTime);
        }
        else
        {
            wait();
        }
    }
    return isConnected();
}

該方法的核心會等待maxWaitTime時間,時間一到,就會返回isConnected()值,這里其實很好理解,就是客戶端發起連接后,這里用一個while循環來等待指定的超時時間,默認是5秒,若5秒過了,就返回isConnected()值,而這里的isConnected()就是驗證是否連接成功了,

那么,這里就剩最后一個答案了,isConnected()是什么?

public synchronized boolean isConnected(){
     return (currentConnectionState != null) && currentConnectionState.isConnected();
}

這里應該是判斷客戶端連接狀態,即在client.start()方法里,會有一個狀態,若創建連接成功,那么currentConnectionState.isConnected()就能得到true值,這里更像是一個觀察模式,觀察指定的連接超時時間內,是否連接成功。

根據debug,發現未連接成功時,值是null,得到的即為false,當我們把默認為5秒的連接超時設置為timeout: 20000,等待連接過程,發現連接成功了,返回currentConnectionState的值為RECONNECTED。

可見,之前出現zookeeper not connected異常問題,就是連接超時設置太短了!
image
currentConnectionState.isConnected()得到的是一個枚舉值,RECONNECTED返回的是true——

  CONNECTED {
        public boolean isConnected() {
            return true;
        }
    },
    SUSPENDED {
        public boolean isConnected() {
            return false;
        }
    },
    RECONNECTED {
        public boolean isConnected() {
            return true;
        }
    },
    LOST {
        public boolean isConnected() {
            return false;
        }
    },
    READ_ONLY {
        public boolean isConnected() {
            return true;
        }
    };

當返回true話,那么!connected就為false,就不會執行以下異常提示了——

if (!connected) {
       throw new IllegalStateException("zookeeper not connected");
}

根據上邊分析,可見啟動Dubbo項目注冊Zookeeper時提示zookeeper not connected異常,是因為沒有在配置里設置連接超時,而是使用了默認的5秒,導致5秒內沒有成功連接,就出現連接異常而無法成功連接,當調長時間后,就正常連接成功了,同時也說明了,這次本地連接zookeeper集群的時間超過了五秒。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM