bigywater.tech
ZooKeeper · 17

Watcher 风暴

一个节点上挂了几百上千个 watcher,它一变化,所有人同时收到通知又同时去读——这就是 Watcher 风暴(惊群)。这篇在 300 个客户端的规模上量通知扇出的时间和惊群时服务端的负载。

51 ms
300 个 watcher 的通知全部到达的时间
91 ms
收到通知后 300 个客户端同时重读并重注册 watch 的总时间
1 ms
惊群时服务端自己统计的最大延迟

一个节点上的很多 watch

实测 · 本机执行输出
  wchs: 300 connections watching 1 paths Total watches:300
  mntr zk_watch_count = 300 | zk_num_alive_connections = 302

一次修改,全部通知

实测 · 本机执行输出
  set 调用自己用时 46.2 ms;300/300 个收到通知:第一个 5 ms、中位 42 ms、最后一个 51 ms
  1 个 watcher:通知在 set 开始后 6.1 ms 到达

通知之后的惊群

实测 · 本机执行输出
  300 个客户端并发重新读取 + 注册 watch 共用 91 ms;单次 get 延迟(客户端侧)中位 11.2 ms、最大 28.9 ms;服务端 zk_max_latency = 1 ms

规模放大后(几千到几万个 watcher)才会成为问题。常见的缓解办法:让 watcher 只监听必要的节点、用「一个监听者 + 本地广播」代替每个客户端各自 watch、在重读前加随机延迟。这些办法本文没有实验验证。

分步演示

Watcher 风暴
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。单机,JVM 堆 256 MiB;300 个 kazoo 客户端是同一进程里的线程(客户端侧的延迟含 Python 线程调度);只测一次
    • 没有验证:上千上万个 watcher、集群里分散在多个节点上的 watcher、通知发送的内部实现、缓解办法的效果
    京ICP备2026067502号-1京公网安备11010502063487号