Zookeeper 脑裂是什么?怎么解决?
结论先行:脑裂是网络分区后集群出现两个“自认为的主”。ZK 靠 quorum 从机制上避免双写:写提案必须得到过半节点确认,分区少数派凑不齐票数,只能停服。真正要防的不是“两个 Leader”,而是旧 Leader 在隔离期间仍对外提供读、以及客户端连到少数派后的长时间不可用。
一、经典脑裂场景
5 节点集群,机房之间光纤被挖断
机房 A:node1(Leader) node2 node3 机房 B:node4 node5
quorum=3,A 侧有 3 台 → 可选举、可写
B 侧只有 2 台 → 无法选主,拒绝写请求
如果 A 侧只有 2 台:
两侧都无法达到 3 票 → 整个集群不可写(宁可不可用,不可双写)
ZK 的设计选择是“少数派直接不可用”,这与 Redis 主从切换的取舍相反。
二、为什么不会真的出现两个 Leader
| 阶段 | 行为 |
|---|---|
| 旧 Leader 失联 | 它仍认为自己是 Leader,继续接受客户端连接 |
| 旧 Leader 提交写 | 提案需要过半 Follower ACK,隔离后凑不齐,写超时失败 |
| 新 Leader 选出 | 分区多数派选出新 Leader,zxid 更大 |
| 旧 Leader 恢复 | 发现集群已有更高 zxid 的 Leader,自动降级为 Follower 并同步数据 |
关键在 zxid:它是单调递增的事务 ID,任何节点恢复后都以最大 zxid 为准,旧状态被覆盖。
三、防护配置
# zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
# 每个 server 一行,observer 不参与投票
server.1=10.0.0.1:2888:3888
server.2=10.0.0.2:2888:3888
server.3=10.0.0.3:2888:3888
server.4=10.0.0.4:2888:3888:observer
部署原则:
1. 节点数取奇数(3/5/7),跨机架、跨可用区打散
2. 不要把 quorum 全部放在同一台物理机/同一个交换机下
3. Observer 扩展读能力,但不增加投票数
4. 客户端连接串写全量地址,避免只连一个分区
四、和 Redis 脑裂的差别
| 维度 | ZK | Redis |
|---|---|---|
| 防双写机制 | quorum 过半确认 | 默认没有,靠 min-replicas-to-write |
| 分区少数派 | 拒绝服务 | 旧主继续接受写,恢复后数据丢失 |
| 数据冲突 | zxid 大者胜,旧数据被覆盖 | 旧主数据被清空或需人工处理 |
| 典型补救 | 无需人工,自动降级 | 需要哨兵/集群模式 + 参数约束 |
五、怎么验证防护是否有效
| 演练 | 期望结果 |
|---|---|
| 停掉 1 台(5 节点) | 集群正常,写延迟略升 |
| 停掉 3 台 | 写请求失败,客户端报连接异常,无脏写 |
| 断开两个机房间网络 | 多数派侧继续服务,少数派侧拒绝写 |
| 恢复网络 | 少数派自动同步,zxid 对齐,无冲突 |
演练时观察三件事:客户端是否快速失败、是否出现双主日志、恢复后数据是否一致。
常见追问
- 追问:ZK 脑裂会导致数据不一致吗?不会出现双写导致的分叉,但少数派会短时间不可用,客户端要处理连接失败与重连。
- 追问:为什么不用偶数节点?4 台的 quorum 是 3,容错仍是 1,和 3 台一样,却多一台成本。
- 追问:Session 超时会不会误判?会。GC 停顿或网络抖动导致心跳丢失,节点被摘除,业务表现为“假死”。调大
sessionTimeout并监控 GC 可以缓解。
小结
- 脑裂的本质是分区后出现两个主,ZK 用 quorum 从写路径上堵死。
- 少数派停服是刻意设计:宁可不可用,不可双写。
- zxid 单调递增保证恢复后旧状态被覆盖。
- 防脑裂更多是部署问题:奇数节点、跨故障域、客户端全地址。