上篇留的悬念,本篇揭晓
上一篇说 ZooKeeper 的写要「过半确认才算提交」,新 Leader 上任时「已提交的写入必然都在」——这两句承诺背后站着的就是 ZAB(ZooKeeper Atomic Broadcast,原子广播协议)。它把 ZooKeeper 集群的日子分成两种状态过:一切正常时跑消息广播,Leader 故障时切换到崩溃恢复,恢复完再切回广播。两副面孔轮流值班,撑起「锁已发给谁绝不翻供」的承诺。
面孔一:消息广播——一个不堵车的 2PC
正常态的写入流程,结构上神似分布式事务系列第 3 篇的 2PC,但两处关键改造让它不堵车:
客户端写请求 → Leader 分配 zxid,生成 PROPOSAL
→ 并行发给所有 Follower
Follower 收到 → 顺序写日志 → 回 ACK
Leader 收到过半 ACK → 发 COMMIT → 全员应用变更 → 回客户端成功改造一:过半就提交,不等全体。2PC 卡在任何一台参与者的回执上,慢一拖全堵;ZAB 只要过半点头就放行,慢节点、宕节点不掉链子——付出的是慢节点可能短暂数据落后的代价(读走各节点本地数据,可能读到刚提交前的值)。改造二:每个 PROPOSAL 带全局单调递增的 zxid,Follower 严格按 zxid 顺序落日志——全集群对「第几号决定」的认识永远一致,乱序无从谈起。对比 Redis 主从复制:Redis 是「主库写完即成功,复制异步慢慢追」,ZAB 是「过半记完账才算成功」——第 7 篇丢锁的窗口,在这里被 quorum 门闩焊死了。
zxid:一个数字里藏着朝代与流水
zxid 是 64 位整数,高 32 位是 epoch(朝代号),低 32 位是本朝内的事务计数器。这个设计一箭双雕:比较两个 zxid,先比朝代——朝代新的一票否决;同朝再比流水——流水大的一定更晚。有了这把标尺,集群里任何两个节点对「谁的日志更新」都能秒级达成一致,不需要任何额外通信。
面孔二:崩溃恢复——带朝代号的选举
Leader 失联(宕机、网络分区),Follower 们进入崩溃恢复:发起选票,每个节点先推荐自己(带上自己的 epoch、zxid、myid),收到别人的选票就比一比,epoch 大者胜,同 epoch 则 zxid 大者胜——谁的数据最新谁当 Leader,数据不齐的落后者根本选不上。胜者当选后把 epoch 加一(改朝换代),把自己全套日志同步给众 Follower,同步完成才开门接客。
两条安全承诺在这里兑现:已提交的事务绝不丢——因为提交要求过半 ACK,新 Leader 必然拥有过半中至少一员的全部日志,选「最新者」必然选中它;未提交的事务必然作废——只在一个节点上晃过的 PROPOSAL 选不上 Leader,自然消亡。最微妙的一幕是旧 Leader 复活:网络分区恢复,被隔离的旧 Leader 重出江湖,发现自己手里还是旧 epoch——朝代号小,立刻自降身份当 Follower,把没提交的提案扔掉,向新 Leader 看齐。epoch 就是防脑裂的终审法官:旧朝的圣旨(未过半的提案)在新朝一律作废。
| 对比项 | Redis 主从复制 | ZAB |
|---|---|---|
| 写入确认 | 主库本地写完即成功 | 过半确认才提交 |
| 故障切换 | 可能丢未同步的写入(第 7 篇丢锁) | 已提交数据必然在新 Leader 上 |
| 脑裂处理 | 靠哨兵仲裁,窗口期双主风险 | epoch 定新旧,旧主自动退位 |
| 性能取向 | 极致性能,牺牲强一致 | 强一致优先,吞吐让路 |
ZAB 的设计哲学用一句话收拢:用「过半点头」换「历史不翻案」,用「朝代更替」防「旧主复辟」。它天生为 ZK 定制,而它的思想在工业界还有一个更流行的通用版本——Raft,etcd 的心脏,第 13 篇再对照细讲。下一篇先享受 ZAB 的红利:手把手看临时顺序节点怎么把分布式锁做成天生公平、自带防死锁。
评论 (0)