本文內(nèi)容為《從PAXOS到ZOOKEEPER分布式一致性原理與實踐》一書學(xué)習(xí)筆記。本文主要概述第七章Watcher機制內(nèi)容。
Water機制概述

Water機制用來實現(xiàn)分布式的通知功能,ZooKeeper允許客戶端向服務(wù)端注冊一個Watcher監(jiān)聽,當服務(wù)端的一些指定事件觸發(fā)了Watcher,會向客戶端發(fā)送事件通知。
1.客戶端注冊Watcher


Watcher接口類用于表示一個標準的事件處理器,包含KeeperState和EventTyepe兩個枚舉類,分別表示通知狀態(tài)和事件類型,同時定義了事件回調(diào)方法process()。在創(chuàng)建ZooKeeper客戶端對象實例時,可以向構(gòu)造方法中傳入一個默認的Watcher。該Watcher即整個ZooKeeper會話期間的默認Watcher。另外,ZooKeeper客戶端也可以通過getData、getChilden和exist三個接口來向ZooKeeper服務(wù)器注冊Watcher。然后把Watcher對象封裝成WatchRegistration對象(暫存數(shù)據(jù)節(jié)點的路徑和Watcher的對應(yīng)關(guān)系),然后將WatchRegistration對象封裝成Packet(在ZooKeeper中,Packet是用于客戶端與服務(wù)器之間網(wǎng)絡(luò)傳輸?shù)淖钚⊥ㄐ艆f(xié)議單元,任何需要傳輸?shù)膶ο蠖夹枰b成一個Packet對象),之后將該Packet放入發(fā)送隊列outgoingQueue中去,等待客戶端發(fā)送出去。底層發(fā)送先進行序列化,只序列化packet中的請求頭和請求體到底層字節(jié)數(shù)組中(并不是完全序列化WatchRegistration對象,如果客戶端注冊的所有Watcher都被傳遞到服務(wù)端的話,服務(wù)端肯定會出現(xiàn)內(nèi)存緊張或其他性能問題),由客戶端線程SendThread進行發(fā)送到服務(wù)器,請求發(fā)送完畢后將該Packet保存到等待響應(yīng)隊列pendingQueue中。之后仍然由SendThread來進行響應(yīng)接收(客戶端SendThread線程的readRespnse方法負責(zé)接收來自服務(wù)端的響應(yīng)),這是一次常規(guī)的響應(yīng)(不是Watch事件),然后從pendingQueue拿出一個packet進行處理,檢驗ZXID確保請求處理的順序性(串行化,書中并沒見這句)。最后客戶端將Watcher從WatchRegistration對象取出轉(zhuǎn)交到ZKWatcherManager中的Map集合dataWatches(路徑和watcher對象的映射)(客戶端已經(jīng)將Watcher暫時封裝在了WatchRegistration對象中,現(xiàn)在就需要從這個封裝對象中再次提取出Watcher,在register方法中,客戶端會將之前暫時保存的的Watcher對象轉(zhuǎn)交給ZKWatcherManager,并最終保存到dataWatches中去。ZKWatcherManager. dataWatches用于將數(shù)據(jù)節(jié)點的路徑和Watcher對象進行一一映射后管理起來。它一個Map<String,Set<Watcher>>類型的數(shù)據(jù)結(jié)構(gòu)。)完成整個注冊過程。

服務(wù)端會進行是否需要注冊watcher的判斷,通過(FinalRequestProcessor.processRequest()中的)getDataRequest.getWatch是來判斷,需要注冊則最終將數(shù)據(jù)節(jié)點、節(jié)點路徑、ServerCnxn(看做watcher對象)(ServerCnxn是一個ZooKeeper客戶端和服務(wù)器之間的連接端口,代表了一個客戶端和服務(wù)器的連接,它實現(xiàn)了Watcher的process接口,因此它可以看成是一個Watcher對象)存儲在WatchManager中(WatchManager是ZooKeeper服務(wù)端Watcher的管理者,其內(nèi)部管理的watchTable 是從數(shù)據(jù)節(jié)點路徑的粒度來托管Watcher,watch2Paths是從Watcher的粒度來控制事件觸發(fā)需要觸發(fā)的數(shù)據(jù)節(jié)點;WatchManager還負責(zé)Watcher事件的觸發(fā)并移除已被觸發(fā)的Watcher)。WatchManager具體由服務(wù)端DataTree中dataWatches和childWatches托管。
2.Watcher觸發(fā)
舉例:節(jié)點數(shù)據(jù)變更的傾聽。會在setData()數(shù)據(jù)修改(對指定節(jié)點進行數(shù)據(jù)更新)后調(diào)用dataWatches.triggerWatch方法進行觸發(fā)。觸發(fā)過程:
1)封裝WatchedEvent。首先將KeeperState、EventType(通知狀態(tài)和事件類型)和節(jié)點路徑(Path)封裝成WatchedEvent對象;
2)查詢Watcher。從WatchManager查詢到watcher并將其取出并刪除(根據(jù)數(shù)據(jù)節(jié)點的節(jié)點路徑從watchTable中取出對應(yīng)的Watcher,如果沒有找到Watcher,說明沒有任何客戶端在該數(shù)據(jù)節(jié)點上注冊過Watcher,直接退出。如果找到了,會將其提取出來,同時會直接從watchTable和watch2Paths中將其刪除),說明觸發(fā)只有一次性。(Watcher在服務(wù)端是一次性的)。
3)調(diào)用process方法來觸發(fā)Watcher。利用watcher(實質(zhì)上是ServerCnxn,代表連接接口)調(diào)用process方法,將WatchedEvent對象包裝成WatcherEvent(實現(xiàn)了序列化接口便于發(fā)送),最后向客戶端發(fā)送該通知。
3.客戶端回調(diào)Watcher
客戶端SendThread線程調(diào)用readRespnse進行處理。
1)反序列化。ZooKeeper客戶端會首先將字節(jié)流反序列化成WatcherEvent對象。
2)處理chrootPath。(如果客戶端設(shè)置了chrootPath屬性,需要對服務(wù)端傳過來的完整的節(jié)點路徑進行chrootPath處理,生成客戶端的一個相對節(jié)點路徑。)
3)還原WatchedEvent。(將WatcherEvent對象轉(zhuǎn)換成WatchedEvent)
4)回調(diào)Watcher。將WatchedEvent對象交給EventThread線程。EventThread . queueEvent會根據(jù)該通知事件從ZKWatcherManager中的Map集合dataWatches(也可能是existWatches/childWatches)取出watcher并移除原來的儲存(客戶端識別出事件類型EventType后從相應(yīng)的Watcher存儲移除),也是一次性觸發(fā)(使用的是remove接口,表明客戶端的Watcher機制也是一次性的)。獲取到所有的Watcher之后,會將其放入waitingEvents這個隊列(待處理Watcher的隊列)中去,EventThread線程的run方法對這個隊列進行處理,最后進行watcher.process方法的真正回調(diào)(此方法中的Watcher才是客戶端真正注冊的Watcher,調(diào)用其process方法就可以實現(xiàn)Watcher的回調(diào)了)。