11.1.4 Watcher机制与事件模型
Watcher 是 ZooKeeper 的一次性事件通知机制,用于在节点变化时通知客户端。客户端在读取数据或获取子节点列表时注册 Watcher,当节点数据变更、节点创建/删除、子节点列表变化或会话状态变化时触发事件。Watcher 触发一次,若需持续监听必须在回调中重新注册。
Watcher 类型与事件模型
- 数据 Watcher:由 getData、exists 注册,关注节点数据或存在性。
- 子节点 Watcher:由 getChildren 注册,关注子节点列表变化。
- 事件类型:NodeCreated、NodeDeleted、NodeDataChanged、NodeChildrenChanged
- 连接状态:SyncConnected、Disconnected、Expired、AuthFailed
事件流与一致性要点
- 事件异步回调,服务器端事务顺序一致,但客户端处理顺序不保证。
- 可能存在事件合并与延迟,“获取数据并设置 Watcher → 收到事件 → 重新读取并再注册”是推荐模式。
- 会话过期后 Watcher 失效,需重连后重新注册。
关键命令与示例(zkCli)#
环境准备:已安装并启动 ZooKeeper,默认端口 2181。
示例 1:数据 Watcher(getData)#
# 连接
./bin/zkCli.sh -server 127.0.0.1:2181
# 创建节点
create /app "v1"
# 读取并设置 Watcher(一次性)
get -w /app
# 另开终端修改数据,触发事件
set /app "v2"
预期效果
- 终端1收到事件:
WATCHER::
WatchedEvent state:SyncConnected type:NodeDataChanged path:/app
示例 2:子节点 Watcher(getChildren)#
# 读取并设置子节点 Watcher
ls -w /app
# 另开终端创建子节点
create /app/child1 "c1"
预期效果
WATCHER::
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/app
示例 3:exists Watcher(监听创建/删除)#
# 监听节点创建
exists -w /tmp/node1
# 另开终端创建
create /tmp/node1 "n1"
# 再次注册监听删除
exists -w /tmp/node1
# 另开终端删除
delete /tmp/node1
回调重注册示例(Java 客户端)#
// Maven: org.apache.zookeeper:zookeeper:3.8.4
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 30000, event -> {
System.out.println("Event=" + event.getType() + " path=" + event.getPath());
// 收到事件后重新注册
try {
if ("/app".equals(event.getPath())) {
zk.getData("/app", true, null);
}
} catch (Exception e) {
e.printStackTrace();
}
});
// 初次注册
byte[] data = zk.getData("/app", true, null);
System.out.println("data=" + new String(data));
常见排错与诊断#
-
Watcher 不触发
- 原因:Watcher 一次性已触发或未正确注册。
- 处理:收到事件后必须再次get -w / ls -w / exists -w。 -
会话过期导致 Watcher 全部失效
- 现象:日志出现Expired,事件不再回调。
- 处理:重连 ZooKeeper 并重新注册所有 Watcher。
# 查看会话状态
stat / | head -n 5
-
惊群效应(大量客户端同时收到事件)
- 处理:通过层级节点拆分、引入队列/锁、限流策略。 -
事件顺序与期望不一致
- 说明:服务器端顺序一致,但客户端处理线程可能乱序。
- 处理:使用版本号(stat)做幂等校验。
# 查看版本号
stat /app
原理草图(ASCII)#
Client A Client B
| getData -w | getData -w
|--------------- |----------------
ZK Server (leader/follower)
|
/app data changed
|
<-- event (once) <-- event (once)
re-register re-register
练习题#
- 使用
get -w监听/app,修改数据并验证事件只触发一次。 - 使用
ls -w监听/app子节点,创建与删除子节点观察事件类型变化。 - 模拟会话过期(停止服务或断开网络),重连后重新注册 Watcher 并验证生效。
- 使用
stat /app观察版本号变化,尝试在回调中基于版本号做幂等处理。