ZooKeeper對Node的增、删、改、查都可以觸發監聽
Watch事件是一次性觸發器,當Watch監視的資料發生變化時,通知設定了該Watch的Client,即watcher(觀察者),Watch事件是異步發送至觀察者。
Watch是一次性觸發的并且在擷取Watch事件和設定新Watch事件之間有延遲,是以不能可靠的觀察每個節點的每一次變化。用戶端監視一個節點,總是先擷取Watch事件,在發現節點的資料變化,Watch事件的順序對應于ZooKeeper服務所見的資料更新的順序。
Zookeeper如何正确設定和擷取watcher
Watcher 設定是開發中最常見的,需要搞清楚watcher的一些基本特征,對于exists、getdata、getchild對于節點的不同操作會收到不同的 watcher資訊。
Watcher是Zookeeper用來實作distribute lock, distribute configure, distribute queue等應用的主要手段。要監控data_tree上的任何節點的變化(節點本身的增加,删除,資料修改,以及孩子的變化)都可以在擷取該資料時注冊一個Watcher,這有很像Listener模式。一旦該節點資料變化,Follower會發送一個notification response,client收到notification響應,則會查找對應的Watcher并回調他們。 有以下接口可以注冊Watcher:
- Stat exists(final String path, Watcher watcher)
- Stat exists(String path, boolean watch)
- void exists(String path, boolean watch, StatCallback cb, Object ctx)
- void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)
- byte[] getData(final String path, Watcher watcher, Stat stat)
- byte[] getData(String path, boolean watch, Stat stat)
- void getData(final String path, Watcher watcher, DataCallback cb, Object ctx)
- void getData(String path, boolean watch, DataCallback cb, Object ctx)
- List<string> getChildren(final String path, Watcher watcher)
- List<string> getChildren(String path, boolean watch)
- void getChildren(final String path, Watcher watcher,ChildrenCallback cb, Object ctx)
操作 | 方法 | 觸發watcher | watcher state | watcher type | watcher path |
Create目前節點 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
set目前節點 | getdata | √ | 3 | 3 | √ |
getchildren | × | × | × | × | |
exists | √ | 3 | 3 | √ | |
delete目前節點 | getdata | √ | 3 | 2 | √ |
getchildren | √ | 3 | 2 | √ | |
exists | √ | 3 | 2 | √ | |
create子節點 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
set子節點 | getdata | × | × | × | × |
getchildren | × | × | × | × | |
exists | × | × | × | × | |
delete子節點 | getdata | × | × | × | × |
getchildren | √ | 3 | 4 | √ | |
exists | × | × | × | × | |
恢複連接配接 | getdata | √ | 1 | -1 | × |
getchildren | √ | 1 | -1 | × | |
exists | √ | 1 | -1 | × | |
恢複連接配接session未逾時 | getdata | √ | -112 | -1 | × |
getchildren | √ | -112 | -1 | × | |
exists | √ | -112 | -1 | × | |
恢複連接配接session逾時 | getdata | √ | 3 | -1 | × |
getchildren | √ | 3 | -1 | × | |
exists | √ | 3 | -1 | × |
注: state = 2 表示删除事件;state = 3表示節點資料變更;state =4表示子節點事件;state = -1表示session事件。 type = -112表示session失效;type = 1表示session建立中;tpye = = 3表示session建立成功。×表示否,√表示是。 如果參數需要傳遞watcher,則可以自己定義Watcher進行回調處理。如果是Boolean型變量,當為true時,則使用系統預設的Watcher,系統預設的Watcher是在zookeeper的構造函數中傳遞的Watcher。如果Watcher為空或者Boolean變量時為false,則表明不注冊Watcher。如果擷取資料後不需要關注該資料是否變化,就不需要注冊Watcher。上面沒有傳回值的都是異步調用模式。需要注意的是,一旦Watcher被調用後,将會從map中删除,如果還需要關注資料的變化,需要再次注冊。 Watcher原理 要搞清楚Watcher的原理,讓我們看看Watcher的工作流程。
- exists方法: 設定watcher時,如果對應服務端已經不存在node時,watcher是不會留在服務端,下次不會被觸發。針對這種情況需要判斷傳回的stat == null來進行處理
- getChildren方法: 和exist一樣,需要處理節點不存在時watcher不會被記錄。 還有一個點,目前的父node發生delete變化時,也可以得到觸發
- getData方法: 和exist一樣,需要處理節點不存在時watcher不會被記錄
要了解watcher是否會丢失,必須要清楚zookeeper整套watcher機制的實作:
- Watcher是一個本地jvm的callback,在和服務端互動過程中是不會進行傳遞的。隻是會将是否有watcher的boolean變量傳遞給server端
- 在服務端,在FinalRequestProcessor處理對應的node操作時,會根據用戶端傳遞的watcher變量,添加到對應的zkDataBase中進行持久化存儲,同時将自己NIOServerCnxn做為一個Watcher callback,監聽服務端事件變化
- leader通過投票通過了某次node變化請求後,通知給對應的follower,follower根據自己記憶體中的zkDataBase資訊,發送notification資訊給zookeeper 用戶端
- zookeeper用戶端接收到notification資訊後,找到對應變化path的watcher清單,挨個進行觸發回調。
可能存在的問題:
1. client向連接配接的server送出了watcher事件後,對應的server還未來得及送出給leader就直接出現了jvm crash,這時對應的watcher事件會丢失。(理論上正常關閉zookeeper server,不會存在該問題,需要用戶端進行重試處理) 2. client在發生一次failover時,可以自動對新的server的notification使用watcher set,可以通過設定jvm變量:zookeeper.disableAutoWatchReset進行,預設為false。如果為true,則不進行自動使用的行為) 3. client出現session expired時,需要重新建立一個zookeeper client執行個體,此時對應的watcher set也會丢失,需要自己編碼做一些額外的處理