bigywater.tech
ZooKeeper · 16

Watcher 一次性触发机制

Watch 触发一次就失效,这个设计带来一个微妙的问题:从触发到重新注册之间的变化怎么办?这篇用 200 次快速写入量出来,再看 3.6 之后的持久 watch 怎么解决它。

147 / 200
通知次数 / 写入次数(重新注册之间的变化没有通知)
最新值不会丢
通知后重新读取,读到的总是注册时刻之后的最新版本
持久 watch
addWatch 注册一次,PERSISTENT_RECURSIVE 覆盖整棵子树

一次性触发与合并

实测 · 本机执行输出
  写了 200 次(终值 200);收到通知 147 次;通过「通知后重新读取」看到的不同的值 147 个
  → 中间的变化被合并了:通知次数 < 写入次数,但最后一次读到的总是最新值(version 单调)

注意:通知次数每次运行都会不同,它取决于写入速度和回调里重新注册的快慢。结论是「少于写入次数」,不是 147 这个数。

持久 watch

实测 · 本机执行输出
  PERSISTENT 模式:3 次 set + 1 次 create 子节点 之后,CLI 收到的事件:
    WatchedEvent state:SyncConnected type:None path:null zxid: -1
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/pw zxid: 289
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/pw zxid: 290
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/pw zxid: 291
    WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/pw zxid: 292
    WatchedEvent state:Closed type:None path:null zxid: -1

持久 watch 是 ZooKeeper 3.6 加入的功能。它不会失效,但也意味着服务端会长期持有这些 watch 的内存,用得过多的后果见第 17、18 篇。

递归持久 watch

实测 · 本机执行输出
  对 /pr 的递归持久 watch,收到:
    WatchedEvent state:SyncConnected type:None path:null zxid: -1
    WatchedEvent state:SyncConnected type:NodeDataChanged path:/pr/a zxid: 359
    WatchedEvent state:SyncConnected type:NodeCreated path:/pr/a/b zxid: 360
    WatchedEvent state:SyncConnected type:NodeCreated path:/pr/c zxid: 361
    WatchedEvent state:SyncConnected type:NodeDeleted path:/pr/c zxid: 362
    WatchedEvent state:Closed type:None path:null zxid: -1

分步演示

一次性与持久
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。单机;一次性 watcher 用 kazoo,持久 watch 用官方 ZooKeeperMain 的交互会话(addWatch);写入 200 次
    • 没有验证:持久 watch 在断线重连后的行为、PERSISTENT_RECURSIVE 对大子树的开销、Curator 的 CuratorCache 如何基于它实现(第 95 篇)、watch 事件的顺序保证
    京ICP备2026067502号-1京公网安备11010502063487号