ZooKeeper 監視點詳解

JiangYue發表於2018-05-04

應用程式需要知道 ZooKeeper 節點狀態的情況很常見,如主從模式中從節點需要知道主節點是否崩潰。

輪訓的方式很低效,尤其是在期望變化頻率很低時。

ZooKeeper 提供了監視點(watch)這一處理節點變化的重要機制,客戶端對指定 znode 節點註冊通知請求,在節點狀態發生變化時就會收到一個單次的通知。

單次通知

監視點是一個單次的觸發器,應用對某個節點註冊了一個監視點,會在該節點第一次觸發事件時嚮應用傳送通知。

監視點與會話關聯,當客戶端與一臺 ZooKeeper 伺服器斷開連線後,會連線到集合中另一臺伺服器,同時將未觸發的監視列表傳送到伺服器。如果所監視的節點已經發生變化,會向客戶端傳送通知,否則會在新的伺服器上註冊監視點。

單次觸發的通知可能會丟失事件,比如在收到通知、註冊新的監視點之間的這段時間所發生的事件。不過丟失事件通常不會造成影響,節點變化可以通過註冊新監視點時讀取節點狀態來捕獲。

設定監視點

ZooKeeper 中的所有讀操作均可設定監視點,包括 getData、getChildren 和 exists。

使用監視點需要實現 Watcher 介面,重寫 void process(WatchedEvent event) 方法。其中 WatchedEvent 包含 ZooKeeper 的會話狀態(KeeperState)和事件型別(EventType)。

事件型別包括 NodeCreated、NodeDeleted、NodeDataChanged、NodeChildrenChanged 和 None。前 3 個型別涉及單個 znode 節點,第 4 個即 NodeChildrenChanged 涉及所監視節點的子節點。None 表示 ZooKeeper 會話狀態發生變化。

事件型別 設定監視點方式
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 位元組的記憶體,開發時需要注意監視點數量。

原文地址

相關文章