bigywater.tech
ZooKeeper · 47

网络分区与集群恢复

真实的网络分区不是「立刻断开」而是「悄悄丢包」。这篇用一个会丢弃节点间流量的 TCP 代理把 leader 隔离出去,按毫秒记录各节点的角色变化,以及分区两侧的写入各是什么结果。

≈ 1 s
悄悄丢包下,follower 发现 leader 丢失的时间(syncLimit × tickTime)
1277 ms
分区开始到多数派一侧的写入成功
被隔离侧的写不保留
恢复后读不到 /from-isolated

各节点的角色时间线

实测 · 本机执行输出
  +  -1001 ms  z47-1 → follower
  +  -1001 ms  z47-2 → leader
  +  -1000 ms  z47-3 → follower
  +    926 ms  z47-1 → 不服务
  +    927 ms  z47-2 → 不服务
  +    928 ms  z47-3 → 不服务
  +   7926 ms  z47-1 → follower
  +   7927 ms  z47-3 → leader
  +   8032 ms  z47-1 → 不服务
  +   8358 ms  z47-3 → 不服务
  +   8903 ms  z47-1 → follower
  +   8904 ms  z47-2 → follower
  +   8905 ms  z47-3 → leader
  (隔离发生在 +0 ms;恢复网络在 +8010 ms)

代理有两种断网方式:立刻关闭连接(检测很快),或悄悄丢包(要等超时)。我最先用了前者,发现节点几乎立刻就发现了故障,这与真实分区不同,所以改成了丢包。

两侧的写入

实测 · 本机执行输出
  当前 leader = z47-3(被隔离的就是它)
  多数派一侧的客户端 create /from-majority:成功(带重试)(分区后 1277 ms 完成,含故障检测与重新选举)
  被隔离的旧 leader 上的那次 create /from-isolated,3 秒后:ready = True
  网络恢复后,被隔离侧那次 create 的结果: ConnectionLoss
  恢复后读两个路径:/from-majority → True | /from-isolated → False
  各节点 Mode: {'z47-1': 'follower', 'z47-2': 'leader', 'z47-3': 'follower'} | Zxid: {'z47-1': '0x400000002', 'z47-2': '0x400000002', 'z47-3': '0x400000002'}

分步演示

网络分区
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。3 节点伪集群,tickTime=500、syncLimit=2;分区用 zkl.NetEnsemble(每对节点之间的 quorum / 选举连接各经过一个 TCP 代理,分区时代理丢弃全部数据但不断开连接,恢复时断开所有连接);客户端端口不经过代理
    • 没有验证:真实网络(iptables / 交换机)下的分区、非对称分区(A 能连 B,B 不能连 A)、分区时间很长、客户端也被分区(分区两侧的客户端行为取决于客户端库)
    京ICP备2026067502号-1京公网安备11010502063487号