在ZooKeeper客戶端與服務端成功完成建立連接后,就建立了一個會話。ZooKeeper會話在整個運行期間的生命周期中,會在不同的會話狀態之間進行切換,這些狀態一般可以分為CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。
Session 是ZooKeeper中最重要的概念之一。它包括4個基本屬性:
sessionID:會話ID,唯一標識一個會話,每次客戶端創建新會話的時候,ZooKeeper都會為其分配一個全局唯一的sessionID。TimeOut:會話超時時間??蛻舳嗽跇嬙靂ooKeeper實例的時候,會配置一個sessionTimeOut參數用于指定會話超時時間。ZooKeeper客戶端向服務器發送這個超時時間后,服務器會根據自己的超時時間限制最終確定會話的超時時間。TickTime:下次會話超時時間點。為了便于ZooKeeper對會話實行“分桶策略”管理,同時也是為了高效低耗地實現的超時檢測與清理,ZooKeeper會為每個會話標識一個下次會話超時時間。isClosing:該屬性用于標記一個會話是否被關閉。通常當服務端檢測到一個會話已經超時失效的時候,會將該會話的isClosing屬性標記為“已關閉”,這樣就能確保不再處理來自該會話的新請求了。
SessionTracker
SessionTracker是ZooKeeper服務端的會話管理器,負責會話的創建、管理和清理工作。每一個會話在SessionTracker內部都保留三份,具體如下。
sessionById:這是一個HashMap<Long,SessionImpl>類型的數據結構,用于根據sessionID來管理Session實體。sessionWithTimeout:這是一個ConcurrentHashMap<Long,Integer>類型的數據結構,用于根據sessionID來管理會話的超時時間。該數據結構和ZooKeeper內存數據庫相連通,會被定期持久化到快照文件中。sessionSets:這是一個HashMap<Long,SessionSet>類型的數據結構,用于根據下次會話超時時間點來歸檔會話,便于進行會話管理和超時檢查。在下文“分桶策略”會話管理的介紹中,我們還會對該數據結構進行詳細講解。
創建連接
服務端對于客戶端的“會話創建”處理,大體可以分為四大步驟,分別是處理ConnectRequest請求、會話創建、處理器鏈路處理和會話響應。在ZooKeeper服務端首先將會由NIOServerCnxn來負責接收來自客戶端的“會話創建”請求,并反序列化出ConnectRequest請求,然后根據ZooKeeper服務端的配置完成會話超時時間的協商。隨后SessionTracker將會為該會話分配一個sessionID,并將其注冊到sessionById和sessionWithTimeout中,同時進行會話激活。之后,該“會話請求”還會在ZooKeeper服務端的各個請求處理器之間進行順序流轉,最終完成會話的創建。
接下來分析一下session狀態之間的轉換:
Session從NOT_CONNECTED狀態開始,并隨著Zookeeper客戶端初始化,轉移到CONNECTING狀態(在圖1中的箭頭1)。 正常情況下,客戶端會與Zookeeper服務器連接成功,并且轉移到CONNECTED狀態(箭頭2)。當客戶端失去了與ZooKeeper服務器的連接或者不能聽到服務器,它會轉移回CONNECTING(箭頭3),并且嘗試尋找另一個ZooKeeper服務器。如果它能找到另一個服務器或者重新連接到之前的服務器,并確認了這個Session仍然有效,它會轉移回CONNECTED狀態。否則,它會定義這個Session失效,并轉移到CLOSED(箭頭4)。應用可以顯示關閉Session。(箭頭4和5)
如果客戶端因為超時和服務器斷開連接,它會保持在CONNECTING狀態。如果這個斷開是因為客戶端和Zookeepe集群的網絡中斷,它將保持在CONNECTING狀態直到它顯示地關閉Session,或者網絡中斷恢復后客戶端從Zookeeper服務器聽到了Session超時消息。我們設計這樣的行為是因為只有Zookeeper集群負責定義Session失效,而不是客戶端??蛻舳瞬荒芏xSession失效,直到聽到Zookeeper Session超時消息。然而,客戶端可以選擇主動關閉這個Session。
在創建Session時,需要設置Session Timeout這個重要參數。這是Zookeeper服務允許一個Session在定義它失效之前的時間。如果服務在時間t內不能看到與一個Session關聯的消息,它將定義這個Session失效。如果客戶端在1/3 t時間內沒有聽到任何從服務器過來的消息,它將發送一個心跳消息給服務器。在(2/3)t時間, Zookeeper客戶端開始尋找另一個Zookeeper服務器,并且它有另外的(1/3)t的時間尋找。
客戶端將連接哪一個服務器
在Quorum模式,一個客戶端擁有多個服務器可以連接。然而在Standalone模式,它必須嘗試重新有效地連接到那個唯一的服務器。在Quorum模式,應該會傳一個服務器列表到客戶端,客戶端從中選擇一個連接。
當嘗試連接另一個服務器時,很重要的一點是這個服務器的ZooKeeper狀態至少要和客戶端已經觀察到的最近ZooKeeper狀態是一樣新的??蛻舳瞬荒苓B接到一個這樣的服務器。它沒有看到客戶端可能已經看到的更新。Zookeeper通過在服務中排序更新操作來決定新鮮程度(Freshness)。每一個對Zookeeper布局狀態的改動操作相對于所有其它執行的更新操作都是全序的,所以如果一個客戶端已經在位置i觀察到一個更新,它不能連接一個僅看到i' < i的服務器。在ZooKeeper的實現中,系統分配給每個更新操作一個事務ID來建立這個順序。
更多精彩內容,歡迎關注微信公眾號:Java小筆記(ijavanote)