zk的session

zk維護的數據主要有:客戶端的會話(session)狀態及數據節點(dataNode)信息。zk在內存中構造了個DataTree的數據結構,維護着path到dataNode的映射以及dataNode間的樹狀層級關係。爲了提高讀取性能,集羣中每個服務節點都是將數據全量存儲在內存中。可見,zk最適於讀多寫少且輕量級數據(默認設置下單個dataNode限制爲1MB大小)的應用場景。數據僅存儲在內存是很不安全的,zk採用事務日誌文件及快照文件的方案來落盤數據,保障數據在不丟失的情況下能快速恢復。上篇文章學習了節點相關的內容,這篇看一下session相關的內容:

指zk客戶端與zk服務器之間的會話,在zk中,會話是通過客戶端和服務器之間的一個TCP長連接來實現的。通過這個長連接,客戶端能夠使用心跳檢測與服務器保持有效的會話,也能向服務器發送請求並接收響應,還可接收服務器的Watcher事件通知。Session的sessionTimeout,是會話超時時間,如果這段時間內,客戶端未與服務器發生任何溝通(心跳或請求),服務器端會清除該session數據,客戶端的TCP長連接將不可用,這種情況下,客戶端需要重新實例化一個Zookeeper對象。在ZooKeeper客戶端與服務端成功完成建立連接後,就建立了一個會話。ZooKeeper會話在整個運行期間的生命週期中,會在不同的會話狀態之間進行切換,這些狀態一般可以分爲CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。Session 是ZooKeeper中最重要的概念之一。它包括4個基本屬性:

  • sessionID:會話ID,唯一標識一個會話,每次客戶端創建新會話的時候,ZooKeeper都會爲其分配一個全局唯一的sessionID。
  • TimeOut:會話超時時間。客戶端在構造ZooKeeper實例的時候,會配置一個sessionTimeOut參數用於指定會話超時時間。ZooKeeper客戶端向服務器發送這個超時時間後,服務器會根據自己的超時時間限制最終確定會話的超時時間。
  • TickTime:下次會話超時時間點。爲了便於ZooKeeper對會話實行“分桶策略”管理,同時也是爲了高效低耗地實現的超時檢測與清理,ZooKeeper會爲每個會話標識一個下次會話超時時間。
  • isClosing:該屬性用於標記一個會話是否被關閉。通常當服務端檢測到一個會話已經超時失效的時候,會將該會話的isClosing屬性標記爲“已關閉”,這樣就能確保不再處理來自該會話的新請求了。

Zk客戶端通過使用語言綁定(language binding)創建一個service的handle,來和zk service建立session。一旦創建,此handle初始爲CONNECTING狀態,然後client將會嘗試與zk service中的一臺server建立鏈接,鏈接成功後,狀態將會被轉換爲CONNECTED。在一般情況下,session會是這兩個狀態中的一種。不過,當發生不可恢復時,例如session過期或者驗證失敗,或者應用明確的關閉了handle,那麼此session的handle將會被變更爲CLOSED狀態。下圖爲session狀態轉換(來自apache zookeeper官網)
在這裏插入圖片描述
當client從zk service中獲取一個handle(句柄)之後,zk將會爲client創建一個session,sessionID爲一個64位的數字。如果client鏈接到了其他的server上,它(client)將會把session id作爲“握手”鏈接的一部分發送給server。因爲安全的因素,server還爲session id創建了一個password,以便任何ZK server都能夠驗證。當session創建成功後,session id和password都將會發送給client。無論client和哪個server建立鏈接,它都必須將session id和password一同發送給需要建立鏈接的server。當client連接失效後,它將會檢索指定的server列表,並與其中一個server重新建立鏈接,session的狀態被再次轉換爲CONNECTED(在session timeout有效期內),或者將會被轉換成“EXPIRED”狀態(session timeout之後,建立了鏈接)。不建議在鏈接失效後,創建新的session(Zookeeper實例),ZK client將會爲你處理重鏈接。此外,ZK內置的一些機制來處理類似“羊羣效應”等等。當client被通知session過期,只需要創建一個新的session即可。

Client與Server持續通訊時,也意味着Session是"活躍"的(sessionId將會伴隨每次請求交付給server),如果session空閒一段時間,這將會導致過期,所以client會發送一種PING類型的請求來保持session的alive,PING請求不僅可以讓server知道client仍然存活,而且它也能夠驗證當前zk server是否alive

會話創建

這裏就講最底層的會話創建以及會話的數據結構,參照 SessionTrackerImpl.SessionImpl數據結構

    public static class SessionImpl implements Session {
        SessionImpl(long sessionId, int timeout, long expireTime) {
            this.sessionId = sessionId;
            this.timeout = timeout;
            this.tickTime = expireTime;
            isClosing = false;
        }

        final long sessionId;//會話id,全局唯一
        final int timeout;//會話超時時間
        long tickTime;//下次會話的超時時間點,會不斷刷新
        boolean isClosing;//是否被關閉,如果關閉則不再處理該會話的新請求

        Object owner;

        public long getSessionId() { return sessionId; }
        public int getTimeout() { return timeout; }
        public boolean isClosing() { return isClosing; }
    }

sessionId唯一性的保證

會話id要保證全局唯一,算法如下

   public static long initializeNextSession(long id) {
        long nextSid = 0;
        nextSid = (System.currentTimeMillis() << 24) >>> 8;
        nextSid =  nextSid | (id <<56);
        return nextSid;
    }

id表示配置在myid文件中的值,通常是一個整數,如1、2、3。該算法的高8位確定了所在機器,後56位使用當前時間的毫秒錶示進行隨機。

會話管理

主要分爲,分桶策略,會話激活,超時檢測,會話清理

分桶策略

Zookeeper的會話管理主要是通過SessionTracker來負責,其採用了分桶策略(將類似的會話放在同一區塊中進行管理)進行管理,以便Zookeeper對會話進行不同區塊的隔離處理以及同一區塊的統一處理。

在這裏插入圖片描述

Zookeeper將所有的會話都分配在不同的區塊中,分配的原則是每個會話的下次超時時間點(ExpirationTime)。ExpirationTime指該會話最近一次可能超時的時間點。同時,Zookeeper Leader服務器在運行過程中會定時地進行會話超時檢查,時間間隔是ExpirationInterval,默認爲tickTime的值,ExpirationTime的計算時間如下:

  ExpirationTime = ((CurrentTime + SessionTimeOut) / ExpirationInterval + 1) * ExpirationInterval

會話激活

會了保持客戶端會話的有效性,客戶端會在會話超時時間過期範圍內向服務端發送PING請求來保持會話的有效性(心跳檢測)。同時,服務端需要不斷地接收來自客戶端的心跳檢測,並且需要重新激活對應的客戶端會話,這個重新激活過程稱爲TouchSession。會話激活不僅能夠使服務端檢測到對應客戶端的存貨性,同時也能讓客戶端自己保持連接狀態,流程如下
在這裏插入圖片描述
如上圖所示,整個流程分爲四步:

  1. 檢查該會話是否已經被關閉。若已經被關閉,則直接返回即可。
  2. 計算該會話新的超時時間ExpirationTime_New。使用上面提到的公式計算下一次超時時間點。
  3. 獲取該會話上次超時時間ExpirationTime_Old。計算該值是爲了定位其所在的區塊。
  4. 遷移會話。將該會話從老的區塊中取出,放入ExpirationTime_New對應的新區塊中。

在上面會話激活過程中,只要客戶端發送心跳檢測,服務端就會進行一次會話激活,心跳檢測由客戶端主動發起,以PING請求形式向服務端發送,在Zookeeper的實際設計中,只要客戶端有請求發送到服務端,那麼就會觸發一次會話激活,以下兩種情況都會觸發會話激活。

  1. 客戶端向服務端發送請求,包括讀寫請求,就會觸發會話激活。
  2. 客戶端發現在sessionTimeout/3時間內尚未和服務端進行任何通信,那麼就會主動發起PING請求,服務端收到該請求後,就會觸發會話激活。

超時檢測

對於會話的超時檢查而言,Zookeeper使用SessionTracker來負責,SessionTracker使用單獨的線程(超時檢查線程)專門進行會話超時檢查,即逐個一次地對會話桶中剩下的會話進行清理。如果一個會話被激活,那麼Zookeeper就會將其從上一個會話桶遷移到下一個會話桶中,如ExpirationTime 1 的session n 遷移到ExpirationTime n 中,此時ExpirationTime 1中留下的所有會話都是尚未被激活的,超時檢查線程就定時檢查這個會話桶中所有剩下的未被遷移的會話,超時檢查線程只需要在這些指定時間點(ExpirationTime 1、ExpirationTime 2…)上進行檢查即可,這樣提高了檢查的效率,性能也非常好。

會話清理

當SessionTracker的會話超時線程檢查出已經過期的會話後,就開始進行會話清理工作,大致可以分爲如下七步。
  1. 標記會話狀態爲已關閉。由於會話清理過程需要一段時間,爲了保證在此期間不再處理來自該客戶端的請求,SessionTracker會首先將該會話的isClosing標記爲true,這樣在會話清理期間接收到該客戶端的心情求也無法繼續處理了。
  2. 發起會話關閉請求。爲了使對該會話的關閉操作在整個服務端集羣都生效,Zookeeper使用了提交會話關閉請求的方式,並立即交付給PreRequestProcessor進行處理。
  3. 收集需要清理的臨時節點。一旦某個會話失效後,那麼和該會話相關的臨時節點都需要被清理,因此,在清理之前,首先需要將服務器上所有和該會話相關的臨時節點都整理出來。Zookeeper在內存數據庫中會爲每個會話都單獨保存了一份由該會話維護的所有臨時節點集合,在Zookeeper處理會話關閉請求之前,若正好有以下兩類請求到達了服務端並正在處理中。

  • 節點刪除請求,刪除的目標節點正好是上述臨時節點中的一個。
  • 臨時節點創建請求,創建的目標節點正好是上述臨時節點中的一個。

對於第一類請求,需要將所有請求對應的數據節點路徑從當前臨時節點列表中移出,以避免重複刪除,對於第二類請求,需要將所有這些請求對應的數據節點路徑添加到當前臨時節點列表中,以刪除這些即將被創建但是尚未保存到內存數據庫中的臨時節點。
  4. 添加節點刪除事務變更。完成該會話相關的臨時節點收集後,Zookeeper會逐個將這些臨時節點轉換成"節點刪除"請求,並放入事務變更隊列outstandingChanges中。
  5. 刪除臨時節點。FinalRequestProcessor會觸發內存數據庫,刪除該會話對應的所有臨時節點。
  6. 移除會話。完成節點刪除後,需要將會話從SessionTracker中刪除。
  7. 關閉NIOServerCnxn。最後,從NIOServerCnxnFactory找到該會話對應的NIOServerCnxn,將其關閉。

參考地址:

https://www.jianshu.com/p/3b7f9a032ded

https://juejin.im/post/5d2d33b5e51d4510803ce461

https://www.jianshu.com/p/594129a44814

https://www.cnblogs.com/aoshicangqiong/p/8024333.html

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