1 基礎概念
ClientCnxn是網絡連接器,管理客戶端與服務端的網絡交互。
ClientWatchManager保存客戶端的watcher
HostProvider服務器地址列表管理器
outgoingQueue客戶端的請求發送隊列
pendingQueue客戶端的服務端響應等待隊列
SendThread管理客戶端與服務端的所有網絡IO
客戶端的SendThread通過一定頻率向服務端發送PING包實現心跳檢測,維護客戶端與服務端的會話生命週期;
會話週期內客戶端與服務端之間出現TCP連接斷開,會自動重連,這個過程是透明的;
SendThread管理客戶端所有的請求發送與響應接收;
SendThread將服務端事件傳遞給EventThread去處理;
EventThread處理客戶端的事件,觸發客戶端註冊的Watcher監聽。
2 客戶端初始化與啓動過程
- 設置默認watcher
- 設置Zookeeper服務器地址列表
- 創建ClientCnxn
創建Zookeeper構造方法中傳入watcher對象,則此watcher對象會被保存入defaultWatcher中,作爲客戶端會話期間默認的watcher。
2.1 會話創建的初始化階段
1 初始化Zookeeper對象,創建的客戶端Watcher管理器ClientWatcherManager
2 設置會話默認Watcher,創建Zookeeper對象是傳入的Watcher對象會被保存入ClientWatcherManager
3 構造Zookeeper服務器地址列表管理器HostProvider
4 創建並初始化客戶端網絡連接起ClientCnxn,管理客戶端與服務端的網絡交互;
同時客戶端會初始化隊列outgoingQueue用於客戶端的請求發送隊列和pendingQueue用於服務端響應的等待隊列;
同時客戶端還要創建ClientCnxnSocket,作爲底層IO處理器
5 初始化網絡線程SendThread管理客戶端與服務端的網絡連接,使用ClientCnxnSocket作爲底層IO處理器
初始化網絡線程EventThread處理客戶端的事件,初始化待處理事件隊列waitingEvents,存放所有等待被客戶端處理的事件。
2.2 會話創建階段
6 啓動SendThread,EventThread
SendThread首先判斷客戶端的狀態,進行一些清理工作,爲客戶端發送”會話創建“請求做準備。
7 獲取一個服務器地址列表
SendThread首先從HostProvider隨機獲取一個Zookeeper地址,交給ClientCnxnSocket去創建與Zookeeper服務器之間的TCP連接。
8 創建TCP連接
獲取到一個服務器地址,ClientCnxnSocket負責與服務器創建一個TCP長連接
9 構造connectRequest請求
SendThread負責構造ConnectRequest,該請求代表客戶端要與服務端連接創建會話。ZK客戶端還會將該請求包裝成網絡層的Packet對象,放入請求發送隊列outgoingQueue中。
10 發送請求
ClientCnxnSocket從outgoingQueue取出一個代發送的Packet對象,序列化成ByteBuffer對象向服務端發送
2.3 響應處理階段
11 接收服務端響應
ClientCnxnSocket接到服務端響應,首先判斷當前客戶端狀態是否是“已初始化”,如果未初始化就認爲此響應是會話創建請求的響應,直接由readConnectResult方法處理該響應。
12 處理Response
ClientCnxnSocket對服務端響應反序列化,得到ConnectResponse對象,並獲取ZK服務端分配的會話SessionId。
13 連接成功
連接成功後,同志SendThread線程進一步設置readTimeout,connectTimeout等參數,並更新客戶端狀態;
通知地址管理器HostProvider當前成功連接的服務器地址。
14 生成事件SyncConnected-None
SendThread會生成事件,目的是上層應用感知會話創建成功。代表客戶端與服務端的會話創建成功,並將該事件傳遞給EventThread線程。
15 獲取Watcher
EventThread收到事件後從ClientWatcherManager中獲取對應的watcher,針對SyncConnected-None直接找出步驟2中存儲的默認watcher,放入EventThread中的waitingEvents隊列中。
16 處理事件
EventThread從waitingEvents隊列中獲取待處理的watcher對象,調用該對象的process方法。
2.4 服務器地址列表
創建ZK客戶端時傳入的服務器地址列表時如何生效的?按照順序連接各個服務器還是隨機連接?
服務器地址列表解析器ConenctStringParser將傳入的地址列表解析成chrootPath,並且保存下服務器地址列表
- chroot指的是每個客戶端爲自己設置命名空間,之後該客戶端的操作都會限定在自己的命名空間內。例如“192.168.1.1:2181, 192.168.1.2:2181/apps/a”
- ConenctStringParser將IP以及port封裝到InetSocketAddress對象,以ArrayList形式保存入ConenctStringParser.serverAddresses屬性中,經過處理的地址列表會被進一步封裝到StaticHostProvider(提供服務器地址個數,地址列表,回調方法用於客戶端與服務端創建連接成功)。
StaticHostProvider實現了接口HostProvider
- 解析地址與端口,封裝成集合
- 調用Collections.shuffle將地址列表打散,組裝成環形循環隊列,使用過程中從這個隊列獲取地址信息
2.5 網絡IO
1 請求發送
outgoingQueue隊列中提取出一個可以發送的Packet對象,同時生成客戶端請求序號XID,並將其設置到Packet請求頭中,然後序列化併發送。
請求發送完畢,立即將Packet保存到pendingQueue中,等待服務端響應後進行相應處理。
2 響應接收
客戶端接到服務端響應後,根據客戶端請求類型不同,進行不同的處理。
客戶端還未初始化,說明客戶端與服務端正在創建會話,直接將接到的ByteBuffer(incomingBuffer)序列化成ConnectResponse對象。
客戶端處於正常的會話週期,並且接到的服務端響應是一個事件,客戶端將接到的ByteBuffer(incomingBuffer)序列化成WatcherEvent對象,並將該對象放入待處理隊列。
如果是一個常規請求(Create,GetData,Exists等操作),從pendingQueue隊列中取出一個Packet對象進行處理。客戶端檢查服務端響應中包含的XID確保請求處理順序,然後將接到的ByteBuffer(incomingBuffer)序列化成Response對象。
finishPacket方法中處理Watcher註冊等邏輯。