應用程序須要知道 ZooKeeper 節點狀態的狀況很常見,如主從模式中從節點須要知道主節點是否崩潰。java
輪訓的方式很低效,尤爲是在指望變化頻率很低時。node
ZooKeeper 提供了監視點(watch)這一處理節點變化的重要機制,客戶端對指定 znode 節點註冊通知請求,在節點狀態發生變化時就會收到一個單次的通知。服務器
監視點是一個單次的觸發器,應用對某個節點註冊了一個監視點,會在該節點第一次觸發事件時嚮應用發送通知。異步
監視點與會話關聯,當客戶端與一臺 ZooKeeper 服務器斷開鏈接後,會鏈接到集合中另外一臺服務器,同時將未觸發的監視列表發送到服務器。若是所監視的節點已經發生變化,會向客戶端發送通知,不然會在新的服務器上註冊監視點。性能
單次觸發的通知可能會丟失事件,好比在收到通知、註冊新的監視點之間的這段時間所發生的事件。不過丟失事件一般不會形成影響,節點變化能夠經過註冊新監視點時讀取節點狀態來捕獲。spa
ZooKeeper 中的全部讀操做都可設置監視點,包括 getData、getChildren 和 exists。code
使用監視點須要實現 Watcher 接口,重寫 void process(WatchedEvent event) 方法。其中 WatchedEvent 包含 ZooKeeper 的會話狀態(KeeperState)和事件類型(EventType)。對象
事件類型包括 NodeCreated、NodeDeleted、NodeDataChanged、NodeChildrenChanged 和 None。前 3 個類型涉及單個 znode 節點,第 4 個即 NodeChildrenChanged 涉及所監視節點的子節點。None 表示 ZooKeeper 會話狀態發生變化。blog
事件類型 | 設置監視點方式 |
---|---|
NodeCreated | 經過 exists 調用設置監視點 |
NodeDeleted | 經過 exists 或 getData 調用設置監視點 |
NodeDataChanged | 經過 exists 或 getData 調用設置監視點 |
NodeChildrenChanged | 經過 getChildren 調用設置監視點 |
ZooKeeper 在 3.4.0 版本添加了原子操做的特性,經過 multiop 能夠原子性地執行多個 ZooKeeper 操做,這些操做要麼所有成功,要麼所有失敗。接口
使用 multiop 時,首先建立出包含 ZooKeeper 操做的 Op 對象,再把多個 Op 對象放到 Iterable 類型的對象中供客戶端調用,如:
Op deleteZnode(String path) {
return Op.delete(path, -1);
}
Op createZnode(String path, String data) {
return Op.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
}
List<OpResult> results = zk.multi(Arrays.asList(deleteZnode("/a"), createZnode("b", "data")));
複製代碼
multi 方法一樣有異步版本,同步和異步方法的定義以下:
public List<OpResult> multi(Iterable<Op> ops) throw InterruptedException,KeeperException;
public void multi(Iterable<Op> ops, MultiCallback callback, Object context);
複製代碼
Transaction 封裝 multi 方法,提供了更簡單的調用方式,能夠經過建立 Transaction 對象來添加操做、提交事務。
List<OpResult> results = zk.transaction().delete("/a", -1).create("/b", "data".getBytes()
, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT).commit();
複製代碼
multi 提供的另外一個功能是檢查 znode 的版本號,在輸入的 znode 節點與版本號不匹配時,multi 會調用失敗。
public static Op check(String path, int version);
複製代碼
ZooKeeper 的狀態會在全部服務器間互相複製,服務端對狀態變化的順序會達成一致,並使用相同的順序對狀態進行更新。但事實上服務端不多同時執行更新操做,若是經過隱藏通道進行通訊可能會出現狀態不一致的現象。
好比 Client-1 鏈接到 Server-1,Client-2 鏈接到 Server-2,/z 節點中的數據由 a 變爲 b。
Server-1 更新狀態會向 Client-1 發送通知,Client-1 收到通知後向 Client-2 發送 /z 節點變化的消息,Client-2 再經過 Server-2 進行操做,若是此時 Server-2 尚未對 /z 節點的狀態進行更新,就會讀取到過時的數據。
Client-1 向 Clinet-2 發送消息,就是隱藏的通道,會致使錯誤發生,正確的作法是 Client-2 只經過 ZooKeeper 服務端接收消息,消除隱藏通道。
當節點發生變化時,會向設置監視點的客戶端發送消息,若是對同一節點設置過多的監控點,就會在節點狀態變動時出現發送消息的高峯,可能會對性能形成影響。
條件容許的話,最好對節點設置少許的監視點,理想狀況下只設置一個。
好比多個客戶端爭相獲取一個鎖:能夠一個客戶端經過建立臨時節點獲取鎖,其餘客戶端對這個節點設置監視點;也能夠改成讓客戶端建立有序臨時節點,最小序號的客戶端獲取鎖,其餘客戶端只對比本身序號小 1 的節點設置監視點。
一個監視點會佔用服務端約 300 字節的內存,開發時須要注意監視點數量。