一、前言
前篇博客分析了Zookeeper的序列化和通信協議,接著繼續學習客戶端,客戶端是開發人員使用Zookeeper最主要的途徑,很有必要弄懂客戶端是如何與服務端通信的。
二、客戶端
2.1 客戶端組成
Zookeeper客戶端主要由如下核心部件構成。
1. Zookeeper實例,客戶端入口。
2. ClientWatchManager, 客戶端Watcher管理器。
3. HostProvider,客戶端地址列表管理器。
4. ClientCnxn,客戶端核心線程,內部包含了SendThread和EventThread兩個線程,SendThread為I/O線程,主要負責Zookeeper客戶端和服務器之間的網絡I/O通信;EventThread為事件線程,主要負責對服務端事件進行處理。
Zookeeper客戶端初始化與啟動環節,就是Zookeeper對象的實例化過程。客戶端在初始化和啟動過程中大體可以分為如下3個步驟
1. 設置默認Watcher
2. 設置Zookeeper服務器地址列表
3. 創建ClientCnxn。
若在Zookeeper構造方法中傳入Watcher對象時,那么Zookeeper就會將該Watcher對象保存在ZKWatcherManager的defaultWatcher中,并作為整個客戶端會話期間的默認Watcher。
2.2 會話的創建
下圖表示了客戶端與服務端會話建立的整個過程,包括初始化階段(第一階段)、會話創建階段(第二階段)、響應處理階段(第三階段)三個階段。
2.3 服務器地址列表
在實例化Zookeeper時,用戶傳入Zookeeper服務器地址列表,如192.168.0.1:2181,192.168.0.2:2181,192.168.0.3:2181,此時,Zookeeper客戶端在連接服務器的過程中,是如何從這個服務器列表中選擇服務器的呢?Zookeeper收到服務器地址列表后,會解析出chrootPath和保存服務器地址列表。
1. Chroot,每個客戶端可以設置自己的命名空間,若客戶端設置了Chroot,此時,該客戶端對服務器的任何操作都將被限制在自己的命名空間下,如設置Choot為/app/X,那么該客戶端的所有節點路徑都是以/app/X為根節點。
2. 地址列表管理,Zookeeper使用StaticHostProvider打散服務器地址(shuffle),并將服務器地址形成一個環形循環隊列,然后再依次取出服務器地址。
2.4 網絡I/O
ClientCnxn是Zookeeper客戶端中負責維護客戶端與服務端之間的網絡連接并進行一系列網絡通信的核心工作類,Packet是ClientCnxn內部定義的一個堆協議層的封裝,用作Zookeeper中請求和響應的載體。Packet包含了請求頭(requestHeader)、響應頭(replyHeader)、請求體(request)、響應體(response)、節點路徑(clientPath/serverPath)、注冊的Watcher(watchRegistration)等信息,然而,并非Packet中所有的屬性都在客戶端與服務端之間進行網絡傳輸,只會將requestHeader、request、readOnly三個屬性序列化,并生成可用于底層網絡傳輸的ByteBuffer,其他屬性都保存在客戶端的上下文中,不會進行與服務端之間的網絡傳輸。
ClientCnxn維護著outgoingQueue(客戶端的請求發送隊列)和pendingQueue(服務端響應的等待隊列),outgoingQueue專門用于存儲那些需要發送到服務端的Packet集合,pendingQueue用于存儲那些已經從客戶端發送到服務端的,但是需要等待服務端響應的Packet集合。
在正常情況下,會從outgoingQueue中取出一個可發送的Packet對象,同時生成一個客戶端請求序號XID并將其設置到Packet請求頭中去,然后序列化后再發送,請求發送完畢后,會立即將該Packet保存到pendingQueue中,以便等待服務端響應返回后進行相應的處理。

客戶端獲取到來自服務端的完整響應數據后,根據不同的客戶端請求類型,會進行不同的處理。
1. 若檢測到此時客戶端尚未進行初始化,那么說明當前客戶端與服務端之間正在進行會話創建,直接將接收的ByteBuffer序列化成ConnectResponse對象。
2. 若當前客戶端已經處于正常會話周期,并且接收到服務端響應是一個事件,那么將接收的ByteBuffer序列化成WatcherEvent對象,并將該事件放入待處理隊列中。
3. 若是一個常規請求(Create、GetData、Exist等),那么從pendingQueue隊列中取出一個Packet來進行相應處理。首先會檢驗響應中的XID來確保請求處理的順序性,然后再將接收到的ByteBuffer序列化成Response對象。
SendThread是客戶端ClientCnxn內部的一個核心I/O調度線程,用于管理客戶端與服務端之間的所有網絡I/O操作,在Zookeeper客戶端實際運行中,SendThread的作用如下:
1. 維護了客戶端與服務端之間的會話生命周期(通過一定周期頻率內向服務端發送PING包檢測心跳),如果會話周期內客戶端與服務端出現TCP連接斷開,那么就會自動且透明地完成重連操作。
2. 管理了客戶端所有的請求發送和響應接收操作,其將上層客戶端API操作轉換成相應的請求協議并發送到服務端,并完成對同步調用的返回和異步調用的回調。
3. 將來自服務端的事件傳遞給EventThread去處理。
EventThread是客戶端ClientCnxn內部的一個事件處理線程,負責客戶端的事件處理,并觸發客戶端注冊的Watcher監聽。EventThread中的watingEvents隊列用于臨時存放那些需要被觸發的Object,包括客戶端注冊的Watcher和異步接口中注冊的回調器AsyncCallback。同時,EventThread會不斷地從watingEvents中取出Object,識別具體類型(Watcher或AsyncCallback),并分別調用process和processResult接口方法來實現對事件的觸發和回調。
三、總結
本篇博文講解了客戶端的相關細節,內容較為簡單易懂,該模塊知識會為之后分析源碼打下好的基礎,謝謝各位園友觀看~