Watcher 一次性通知
先给答案:WatchManager、DataTree 触发路径和客户端事件线程
Section titled “先给答案:WatchManager、DataTree 触发路径和客户端事件线程”WatchManager、DataTree 触发路径和客户端事件线程。 正文沿“调用链 -> 为什么不是持续订阅”展开:先确认入口和状态归属,再跟踪控制流或数据流的推进,最后落到对外可观察的结果。
主要失效边界集中在“先读后注册、回调阻塞、事件合并”这些场景。它们破坏的是容量、顺序、并发或生命周期前提;排查时应先确认状态是否仍由正确对象持有,再核对推进条件和清理路径。
getData(path, watch=true) -> WatchManager#addWatch -> DataTree mutation -> triggerWatch -> ClientCnxn.EventThread -> user Watcher.processDataTree 为 data、existence、child 三类 watch 使用不同集合;修改节点时触发对应路径并从集合移除。客户端把网络线程收到的事件交给 EventThread,不在 IO 线程执行用户回调。
关键坐标:
WatchManager#addWatch—zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java:104WatchManager#triggerWatch—zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java:205WatchManager#removeWatcher—zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java:165DataTree#addDataNode—zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java:495DataTree#deleteNode—zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java:604DataTree#setData—zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java:647DataTree#processTxn—zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java:302ClientCnxn#queuePacket—zookeeper-client/zookeeper-client/src/main/java/org/apache/zookeeper/ClientCnxn.java:1050ClientCnxn.EventThread#run—zookeeper-client/zookeeper-client/src/main/java/org/apache/zookeeper/ClientCnxn.java:1162
为什么不是持续订阅
Section titled “为什么不是持续订阅”替代方案:服务端为每个客户端保存无限事件队列。为什么不行:慢客户端会积压内存,重连后的事件顺序也难定义。证据:Watcher 触发后从路径集合删除,客户端重新读取当前状态。
| 场景 | 现象 | 原因 | 规避 |
|---|---|---|---|
| 先读后注册 | 丢变化 | 注册与读取存在竞态 | 使用带 watch 的 API |
| 回调阻塞 | 后续事件延迟 | EventThread 串行 | 回调只投递任务 |
| 事件合并 | 少于预期事件数 | 不承诺每个中间状态 | 读取最终状态 |
一次性通知适合“状态变化,请重新读取”;通知应小,真实状态以可重复读取的快照为准。
面试锚点
- Watcher 为什么一次性?
- 如何避免读取和注册之间的竞态?
- 为什么不能在回调里做阻塞操作?