zookeeper源碼閱讀分析筆記–用戶端服務端通訊機制以及session逾時、到期處理

來源:互聯網
上載者:User

    這兩天看了一下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

 

 

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.