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

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