zookeeper的watcher是一次性的嗎?!

 
zookeeper

zookeeper 是流行的高性能分佈式協調工具,它提供了分佈式環境中須要的命名服務,配置管理,分佈式鎖,註冊中心,Leader 選舉等等功能,應用十分普遍。html

zookeeper 的 watcher

Client 能夠在 zookeeper 的節點(znode)上設置監聽器(watcher),當節點被修改時,zookeeper 會向客戶端發送一個相應的通知。 能夠經過 getData(),getChildren() 和 exists() 三個方法來設置 watcher。以 getData() 的方法簽名爲例:node

public byte[] getData(String path, boolean watch, Stat stat);

方法的第二個參數便表示是否在當前 path 上註冊 watcher。apache

watcher 流程

watcher 的註冊與觸發流程大體以下: Watcher Flowless

爲了高性能考慮,zookeeper 的 watcher 被設計成很輕量級。上圖 Client 與 Server 交互過程當中,Client 並無把 watcher 實例傳給 Server 端,而只是設置一個標識位告訴它要監聽某個節點的變化。同理,Server 端節點發生變化的時候,只能簡單「通知」 Client 變化的事件,而具體發生了什麼變化,則須要 Client 本身去 Server 端再次獲取。分佈式

watcher 是一次性的嗎?

對 zookeeper 有過簡單瞭解的同窗,估計都會認爲 watcher 是一次性的,包括官網都是這麼介紹 watcher 的:ide

One-time trigger: One watch event will be sent to the client when the data has changed. For example, if a client does a getData("/znode1", true) and later the data for /znode1 is changed or deleted, the client will get a watch event for /znode1. If /znode1 changes again, no watch event will be sent unless the client has done another read that sets a new watch.工具

是的,普通的 watcher 確實是一次性的,當事件被觸發以後,所對應的 watcher 會被立馬刪除。如此設計的緣由,依然是出於性能考慮(咱 zookeeper 是一個高性能的分佈式協調工具!)。試想,若是 Server 端的每次數據變動都通知 Client,在更新很是頻繁的狀況下會影響性能。同時,Client 其實並不必定想知道每次數據更新,更多的是須要知道最新的數據。 不過若是 Client 想繼續監聽節點變化,如何註冊一個永久的 watcher 呢?zookeeper 提供的方案是,在後續如 getData() 獲取 Server 端最新數據的時候再註冊一遍 watcher便可(同時這也是 Curator 添加永久 wathcher 的作法),這也是沒有單獨提供一個 addWatcher() 方法而須要與 getData() 綁定使用的緣由。 因此 zookeeper 這麼設計是情有可原的。性能

持久遞歸監聽器(Persistent Recursive Watch)

在上文中咱們知道,出於性能考慮,zookeeper 把 watcher 設計爲一次性的,若是須要繼續監聽節點變化,則須要調用如 getData() 從新註冊。然而在 zookeeper 3.6.0 中新增了以下方法:url

private void addPersistentWatches(String clientPath, Set<Watcher> result);

官網的介紹以下:.net

New in 3.6.0: Clients can also set permanent, recursive watches on a znode that are not removed when triggered and that trigger for changes on the registered znode as well as any children znodes recursively.

也就是說,在 3.6.0 版本以後,zookeeper 支持設置永久的 watcher 了!來看看這個永久遞歸監聽器的特性:

  1. 支持 watcher 所支持的全部語義:CHILDREN,DATA 和 EXISTS。
  2. 能夠在任意節點上添加 CHILDREN 和 DATA 遞歸監聽。
  3. 能夠在任意路徑上添加 EXISTS 遞歸監聽。
  4. 添加一個遞歸監聽器至關於在其全部子節點上添加 watcher
  5. 跟普通的 watcher 同樣,當遞歸監聽器的某個子節點事件被觸發以後,在對其調用 getData() 以前,不會再次觸發事件。
  6. 遞歸監聽器會覆蓋其子節點上的全部普通 watcher。

咱們主要看第5條,舉兩個例子,假如咱們在節點 /a 上添加了永久遞歸監聽器:

狀況一:

  • 當 /a/a 變動時,將會觸發監聽器
  • 當 /a/a 再次變動時,不會觸發監聽器
  • Client 調用 get(/a/a) 以後,該子節點上的監聽器被「自動重置」
  • 當 /a/a 再次變動時,又會觸發監聽器

狀況二:

  • 當 /a/a 變動時,將會觸發監聽器
  • 當 /a/b 變動時,將會觸發監聽器
  • Client 調用 get(/a/a) 以後,該子節點上的監聽器被「自動重置」
  • 當 /a/b 再次變動時,不觸發監聽器
  • 當 /a/a 再次變動時,又會觸發監聽器

不難看出,永久遞歸監聽器雖然是永久的,但它與普通的 watcher 設計理念實際上是一致的,也就是說,它保證了在事件觸發和 Client 調用 getData() 獲取數據 兩個動做之間,沒有中間事件,依然能保持 zookeeper 的高性能。 實際上是由於該監聽器是遞歸的,因此將它設置成是永久的。做者權衡過兩種實現方式:

  1. 給節點的子節點分別添加一個 watcher
  2. 只在目標節點添加一個永久的 watcher,而後將已經觸發的子節點事件記錄在一個黑名單(blacklist)中

結果是採用了而第二種方式,由於它所需的內存更小。而在 Server 端監聽器的重置,實際上只是將對應的子節點路徑從黑名單中移除。

Curator 的支持

固然,Curator 也在 5.0.0 版本中添加了對持久遞歸監聽器的支持:

/**
 * client - curator client
 * basePath - 目標節點路徑
 * recursive - 遞歸與否
 **/
public PersistentWatcher(CuratorFramework client, String basePath, boolean recursive);
總結

爲了實現高性能的目的,zookeeper 的 watcher 被設計成是輕量級,一次性的。若是客戶端須要持續訂閱某個節點的狀態,須要每次在 getData() 方法裏面重複註冊 watcher。 在 3.6.0 版本以後,zookeeper 提供了永久遞歸 watcher,它是永久的。但其行爲跟普通的 watcher 一致,即在通知發送給 Client 與 Client 調用 getData() 之間,不會有中間事件觸發。其原理實際上是,在 Client 調用 getData() 的時候,在 Server 端自動將該 watcher 從新註冊了。

相關文章
相關標籤/搜索