bigywater.tech
ZooKeeper · 83

用 ZooKeeper 实现分布式屏障

让一批进程「等齐了再一起开始」「都做完了再一起结束」,这就是栅栏。ZooKeeper 上有两种配方:单次放行的 Barrier,和进出都同步的 DoubleBarrier。这篇量放行的同步程度、重复使用、人数不齐会怎样,以及参与者崩溃时会怎样。

13 ms
删除 /bar 后 10 个等待者全部放行的跨度
2 ms
DoubleBarrier 5 个参与者离开时刻的最大差
3.6 s
参与者 kill -9 后其余参与者 leave 返回的时间

Barrier:一个 znode 的开关

实测 · 本机执行输出
  /bar 存在: True (Barrier 的「关上」就是这个 znode 存在)
  1 秒后仍被挡住的等待者数 = 10
  remove() 之后 10 个都被唤醒:最早 11.0 ms,最晚 13.3 ms
  已经移除后再来的等待者:wait() 立即返回: 0.8 ms

等待者 watch 这个 znode 的删除事件;控制者删掉它就是放行。10 个 watcher 同时被触发(单个路径,惊群规模取决于等待者数量,见watch 风暴)。

DoubleBarrier:进出都同步

实测 · 本机执行输出
  p0: 调用 enter  0.0s → 实际进入  2.0s;干活 0.2s,调用 leave  2.2s → 实际离开  3.1s
  p1: 调用 enter  0.0s → 实际进入  2.0s;干活 0.4s,调用 leave  2.4s → 实际离开  3.1s
  p2: 调用 enter  0.0s → 实际进入  2.0s;干活 0.6s,调用 leave  2.6s → 实际离开  3.1s
  p3: 调用 enter  0.0s → 实际进入  2.0s;干活 0.8s,调用 leave  2.8s → 实际离开  3.1s
  p4: 调用 enter  2.0s → 实际进入  2.0s;干活 1.0s,调用 leave  3.0s → 实际离开  3.1s
  进入时刻最大差 0 ms;离开时刻最大差 2 ms

每个参与者在 /dbar 下建一个临时节点;子节点数达到要求人数时进入放行,离开时节点逐个删除、删光时放行。典型用法:一批 worker 等所有人就绪再开始计算、全做完再汇总。

可重复使用

实测 · 本机执行输出
  第二轮完成的参与者数 = 5 / 5;/dbar 剩余子节点 = []

人数不齐

实测 · 本机执行输出
  4 个都还在等(没有 timeout 参数的 enter 会永远阻塞):已返回 0 个;/dbar3 子节点 4 个
  kazoo 的 DoubleBarrier.enter() 没有 timeout 参数,没人来就一直等。

这是用 DoubleBarrier 最容易踩的坑:启动一个参与者失败了,其余的就卡在 enter() 上,没有任何报错。

参与者崩溃

实测 · 本机执行输出
  3 个参与者全部 ENTERED 后 kill -9 其中一个,另外两个 LEFT 的时刻(相对 kill): ['3.6s', '3.6s'](会话超时 3 s)

节点是临时节点,所以崩溃的参与者最终会被清除,leave 不会永远卡住,但要等一个会话超时。对称地,如果参与者在 enter 之前崩溃,其余人则永远等(上一节)。

分步演示

分布式栅栏
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。kazoo 2.11.0 的 Barrier 与 DoubleBarrier;前三项用线程内的独立客户端,崩溃测试用独立子进程,会话超时 3 s
    • 没有验证:DoubleBarrier 的 leave 超时、参与者在 enter 之前崩溃后的补位、数百个参与者时的放行跨度、Curator 的 DistributedBarrier / DistributedDoubleBarrier(见第 92 篇)
    京ICP备2026067502号-1京公网安备11010502063487号