ZooKeeper之Session
會話是ZooKeeper中最重要的概念之一,客戶端與服務端之間的互動操作就是依賴於ZooKeeper的會話機制。接下來分別從客戶端與服務端兩方面來講解會話的建立流程與實現細節。
1.建立會話客戶端流程
客戶端分為初始化階段、會話建立階段和響應處理階段。
初始化階段
1.初始化ZooKeeper物件:通過例項化一個ZooKeeper物件,在初始化過程中,建立一個客戶端Watcher管理器:ClientWatchManager。
2.設定會話預設Watcher:如果在構造方法中傳入了一個Watcher物件,那麼客戶端會將這個物件作為預設Watcher儲存在ClientWatcheManager。
3.構造ZooKeeper伺服器地址列表管理器:對於構造方法傳入的伺服器地址,客戶端會將其放在地址列表管理器HostProvider中。
4.建立並初始化客戶端網路聯結器:ZooKeeper客戶端首先會建立一個網路聯結器ClientCnxn,用來管理客戶端與伺服器的網路互動,同時建立了ClientCnxn聯結器的底層I/O處理器ClientCnxnSocket。另外,還會初始化客戶端兩個核心佇列outgoingQueue和pendingQueue,分別作為客戶端的請求傳送佇列和服務端響應的等待佇列。
5.初始化SendThread和EventThread:SentThread和EventThread是ClientCnxn的兩個內部類,也是客戶端的兩個核心執行緒,前者用於管理客戶端和服務端之間的所有網路I/O,後者則用於進行客戶端的事件處理。同時,客戶端還會將ClientCnxnSocket分配給SendThread作為底層網路I/O處理器,並初始化EventThread的待處理事件佇列waitingEvents,用於存放所有等待被客戶端處理的事件。
會話建立階段
6.啟動SendThread和EventThread:SendThread首先會判斷當前客戶端的狀態,進行一系列請理性工作,為客戶端傳送"會話建立"請求做準備。
7.獲取一個伺服器地址:在開始建立TCP連線之前,SendThread首先會從HostProvider中隨機獲取出一個地址,然後委託給ClientCnxnSocket去建立與ZooKeeper伺服器之間的TCP連線。
8.建立TCP連線:獲取到一個伺服器地址後,ClientCnxnSocket負責和伺服器建立一個TCP長連線。
9.構造ConnectRequest請求:SendThread會負責根據當前客戶端的實際設定,構造出一個ConnectRequest請求,該請求代表客戶端試圖與服務端建立一個會話。同時,ZooKeeper客戶端還會進一步將該請求包裝成網路I/O層的Packet物件,放入請求傳送佇列outgoingQueue中去。
10.傳送請求:ClientCnxnSocket負責從outgoingQueue中取出一個待發送的Packet物件,將其序列化成ByteBuffer後,向服務端進行傳送。
響應處理階段
11.接收服務端響應:ClientCnxnSocket接收到服務端的響應後,會首先判斷當前的客戶端狀態是否是"已初始化",如果尚未完成初始化,那麼就認為該響應一定是會話建立請求的響應,直接交由readConnectResult方法來處理該響應。
12.處理Response:ClientCnxnSocket會對接收到的服務端響應進行反序列化,得到ConnectResponse物件,並從中獲取到ZooKeeper服務端分配的會話sessionId。
13.連線成功:一方面需要通知SendThread執行緒,進一步對客戶端進行會話引數的設定,包括readTimeout和connectTimeout等,並更新客戶端狀態;另一方面,需要通知地址管理器HostProvider當前成功連線的伺服器地址。
14.生成事件SyncConnected-None:為了能夠讓上層應用感知到會話的成功建立,SendThread會生成一個SyncConnected-None,代表客戶端與伺服器會話建立成功,並將該時間傳遞給EventThread執行緒。
15.查詢Watcher:EventThread執行緒接收到事件後,會從ClientWatchManager管理器中查詢出對應的Watcher,針對SyncConnected-None事件,那麼就直接找出步驟2中儲存的預設Watcher,然後將其放到EventThread中的waitingEvents佇列中,
16.處理事件:EventThread不斷地從waitingEvents佇列中取出待處理的Watcher物件,然後直接呼叫該物件的process介面方法。
2.會話建立服務端流程
請求接收
1.I/O層接收來自客戶端的請求:ZooKeeper中,NIOServerCnxn例項維護每一個客戶端連線,其負責統一接收來自客戶端的所有請求,並將請求內容從底層網路I/O中完整地讀取出來。
2.判斷是否是客戶端"會話建立"請求:如果尚未被初始化,那麼就可以確定該客戶端請求一定是"會話建立"請求。
3.反序列化ConnectRequest請求:一旦確定當前客戶端請求是“會話建立”請求,那麼服務端就可以對其進行反序列化,並生成一個ConnectRequest請求實體。
4.判斷是否是ReadOnly客戶端:在ZooKeeper的設計實現中,如果當前ZooKeeper伺服器是以ReadOnly模式啟動的,那麼所有來自非ReadOnly型客戶端的請求將無法被處理。
5.檢查客戶端ZXID:正常情況下,同一個ZooKeeper叢集中,服務端的ZXID值必定大於客戶端的ZXID。
6.協商sessionTimeout:客戶端向伺服器傳送這個超時時間後,伺服器會根據自己的超時時間限制最終確定該會話的超時時間。
7.判斷是否需要重新建立會話:服務端根據客戶端請求中是否包含sessionID來判斷該客戶端是否需要重新建立會話。如果客戶端請求中包含了SessionID,那麼就認為該客戶端正在進行會話重連,只需要重新開啟這個會話即可,否則需要重新建立。
會話建立
8.為客戶端生成sessionID:在為客戶端建立會話之前,服務端首先會為每個客戶端都分配一個sessionID.
9.註冊會話:向SessionTracker註冊會話。SessionTracker中維護了兩個比較重要的資料結構,分別是sessionsWithTimeout和sessionsById。前者根據sessionID儲存了所有會話的超時時間,而後者根據sessionID儲存了所有會話實體。
10.啟用會話:為會話安排一個區別,以便會話清理程式能夠高效地進行會話清理。
11.生成會話密碼:服務端在建立一個客戶端會話的時候,會同時為客戶端生成一個會話密碼,連同sessionID一起傳送給客戶端,作為會話在叢集中不同機器間轉移的憑證。
預處理
12.將請求交給ZooKeeper的PrepRequestProcessor處理器進行處理:在提交該請求處理器之前,需要確保當前會話處於啟用狀態。
13.建立請求事務頭:對於請求事務,ZooKeeper首先會為其建立請求事務頭,包括sessionID、ZXID、CXID和請求型別等。
14.建立請求事務體:對於事務請求,ZooKeeper還會為其建立請求事務體。
15.註冊與啟用會話:此處會話註冊與啟用的目的是處理非Leader伺服器轉發過來的會話建立請求。
事務處理
16.將請求交給ProposalRequestProcessor處理器:該處理器涉及事務請求的投票流程,流程分別為Sync流程(使用SyncRequestProcessor處理器記錄事務日誌的過程)、Proposal流程(投票過程)和Commit流程(CommitProcessor處理器將請求放入queuedRequests佇列,需要等待其它事務請求的投票過程結束再作提交處理)。
事務應用
17.交付給FinalRequestProcessor處理器:FinalRequestProcessor處理器首先檢查outstandingChanges佇列中請求的有效性,如果發現這些請求已經落後於當前正在處理的請求,那麼直接從outstandingChanges佇列中移除。
18.事務應用:需要將事務變更應用到記憶體資料庫中。如果是會話建立則無需對記憶體資料庫做任何處理。
19.將事務請求放入佇列:一旦完成事務請求的記憶體資料庫應用,就可以將請求放入commitProposal佇列中。該佇列用來儲存最近被提交的事務請求,以便叢集間機器進行資料的快速同步。
會話響應
20.統計處理:統計客戶端連線的基本資訊。
21.建立響應ConnectResponse:ConnectResponse是一個會話建立成功後的響應,包含了當前客戶端與服務端之間的通訊協議版本號、會話超時時間、sessionID和會話密碼。
22.序列化ConnectResponse。
23.I/O層傳送給客戶端