zookeeper的分析源碼方面看timeout

    這兩天看了一下zookeeper的相關的源碼,版本基於3.4.5,代碼結構還是比較清晰的;
這裏重點分析一下zookeeper client和server端之間的通信以及相關的異常處理機制。  
1、客戶端
  客戶端幾個主要的類爲Zookeeper、ClientCnxn、SendThread、ClientCnxnSocketNIO。
客戶端通過Zookeeper相關的API和server進行同步或者異步通信,包括create、delete、exists、getChildren、getData、setData、sync等,
在實現中zookeeper可以調用ClientCnxn.submit同步等待返回結果或者調用ClientCnxn.queuePacket把消息放到outgoing隊列中異步發送並提供callback進行異步回調;
ClientCnxn負責管理client端的socket IO,維護zookeeper server列表,透明的在server之間連接切換;ClientCnxn有一個SendThread後臺線程,
負責對outgoing queue中的消息發送和server端返回消息接收;ClientCnxnSocketNIO代表一個server端的NIO socket 長連接連接,負責和server端底層進行通信。
SendThread服務於queueQueue 進行package的發送接收,並啓動後臺線程心跳建立連接;pendingQueue用於存放已經發送出去,未回覆的包,在收到回覆後,
從pendingQueue隊列中刪除;SendThread從socket讀取服務端返回的結果後,通過readResponse對消息進行處理,根據返回的replyHeader中xid進行相應的後續處理,
當xid=-2時候,表示這是一個ping的返回,當xid=-4,代表這是一個auth 的返回,當xid=-1時候,代表這是一個zookeeper節點變化導致的通知watch執行的消息返回,
返回的消息用watchEvent包裝,發送到EventThread中的waitingEvents隊列中,EventThread後臺線程從隊列中拉取消息執行watcher中的process邏輯。

   客戶端和服務端之間的NIO socket連接模型這裏就不多說了,以前的NIO 系列blog中對這些有比較多的闡述,
這裏說一下客戶端和服務端的長連接的session失效、超時、連接斷掉的問題,客戶端又是如何處理的;
ClientCnxn包括這幾個變量,
private int connectTimeout;
private volatile int negotiatedSessionTimeout;
private int readTimeout;
private final int sessionTimeout;在client連接到server後,server返回給client確認信息(包括服務器返回給客戶端的真實的timeout時間--negotiatedSessionTimeout),
client read結果(SendThread.run-->ClientCnxnSocketNIO.doTransport-->doIO),設置相關的timeout參數。
在這裏初始化,
   sendThread.onConnected(conRsp.getTimeOut(), this.sessionId,
                conRsp.getPasswd(), isRO);
比如,
           readTimeout = negotiatedSessionTimeout * 2 / 3;
            connectTimeout = negotiatedSessionTimeout / hostProvider.size();
            hostProvider.onConnected();
            sessionId = _sessionId;
            sessionPasswd = _sessionPasswd;
這些timeout代表client端在超時的這段時間裏,沒有讀到從server端返回的消息(比如發送ping 數據到server,server給返回信息)
在ClientCnxnSocketNIO.doTransport中,進行select(waitTimeOut)操作,先updateNow
如果有socket可以讀數據,則讀數據後,updateLastRec,沒數據可讀的話(不更新updateLastRec),下次SendThread.run循環,就可能會出現讀超時。
在SendThread.run循環中判斷是否超時
                           to = readTimeout - clientCnxnSocket.getIdleRecv();//(now - lastHeard);
                    } else {
                        to = connectTimeout - clientCnxnSocket.getIdleRecv();
                    }
                    if (to <= 0) {
//超時
                        throw new SessionTimeoutException(
                                "Client session timed out, have not heard from server in "
                                        + clientCnxnSocket.getIdleRecv() + "ms"
                                        + " for sessionid 0x"
                                        + Long.toHexString(sessionId));
                    }
client不斷的發送sendPing()心跳,以維持在server端的session有效。
   if (state.isConnected()) {
                        int timeToNextPing = readTimeout / 2
                                - clientCnxnSocket.getIdleSend();
                        if (timeToNextPing <= 0) {
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }
在SendThread.run循環中,client發送sendPing()心跳,以維持在server端的session有效。
   if (state.isConnected()) {
                        int timeToNextPing = readTimeout / 2
                                - clientCnxnSocket.getIdleSend();
                        if (timeToNextPing <= 0) {
                            sendPing();
                            clientCnxnSocket.updateLastSend();
                        } else {
                            if (timeToNextPing < to) {
                                to = timeToNextPing;
                            }
                        }
                    }
如果客戶端訪問服務端而服務端認爲客戶端已經超時了或者服務端宕機時,客戶端會調用SendThread.cleanup操作,銷燬sockect,
把sockKey設置爲null,這樣在SendThread.run的while循環中會判斷isConn,進而重新連接一個server.
參見SendThread.run中的異常處理部分
      if (e instanceof SessionExpiredException) {//從服務端拋出的
                            LOG.info(e.getMessage() + ", closing socket connection");
                        } else if (e instanceof SessionTimeoutException) {//客戶端拋出的
                            LOG.info(e.getMessage() + RETRY_CONN_MSG);
                        } else if (e instanceof EndOfStreamException) {
                            LOG.info(e.getMessage() + RETRY_CONN_MSG);
                        } else if (e instanceof RWServerFoundException) {
                            LOG.info(e.getMessage());
                        cleanup();//在cleanup中銷燬sockect,把sockKey設置爲null,這樣在SendThread.run的while循環中會判斷isConn,進而重新連接一個server.
         } 
ClientWatchManager管理客戶端所有得watcher,並進行分類,dataWatches、existWatches、childWatches。

 


2、服務端
NIOServerCnxnFactory,服務端進行NIO操作(Select,Channel)的類,每建立一個客戶端連接,就生成一個NIOServerCnxn,
這裏說一下session的超時時間設置,
socket channel收到消息建立連接請求的時候,NIOServerCnxn.readPayload-->NIOServerCnxnreadConnectRequest()--->zkServer.processConnectRequest(this, incomingBuffer);
ZookeeperServer,可見如果客戶端發來的sessionTimeout超過min-max這個範圍,server會自動截取爲min或max
   minSessionTimeout 單位毫秒。默認2倍tickTime
   maxSessionTimeout 單位毫秒。默認20倍tickTime
  (tickTime也是一個配置項。是Server內部控制時間邏輯的最小時間單位)

public void processConnectRequest(ServerCnxn cnxn, ByteBuffer incomingBuffer)
        int minSessionTimeout = getMinSessionTimeout();
        if (sessionTimeout < minSessionTimeout) {
            sessionTimeout = minSessionTimeout;
        }
        int maxSessionTimeout = getMaxSessionTimeout();
        if (sessionTimeout > maxSessionTimeout) {
            sessionTimeout = maxSessionTimeout;
        }
        cnxn.setSessionTimeout(sessionTimeout);
SessionTracker保存客戶端session的列表,判斷session是否過期,一種是在處理服務端請求的packg,一種是LearnerHandler.run後臺線程運行,
會調用SessionTracker.touchSession(ServerCnxn cnxn)進行判斷是否超時間
   void touch(ServerCnxn cnxn) throws MissingSessionException {
        if (cnxn == null) {
            return;
        }
        long id = cnxn.getSessionId();
        int to = cnxn.getSessionTimeout();
        if (!sessionTracker.touchSession(id, to)) {//無論client是重連,還是其他,超過session timeout時候沒連上,就表示session過期了
            throw new MissingSessionException(
                    "No session with sessionid 0x" + Long.toHexString(id)
                    + " exists, probably expired and removed");
        }
    }

如果session過期就刪除session信息,包括這個會話創建的臨時節點和註冊的Watcher

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章