分布式系统【五、ZAB 协议与 ZooKeeper 一致性实现(超详细)】
ZAB 协议与 ZooKeeper 一致性实现(超详细)
系列专题第五篇 · 分布式系统基础指南
1. 引言与整体认识
ZAB(ZooKeeper Atomic Broadcast) 是 ZooKeeper 的核心一致性协议。它不是通用的“共识算法论文”(如 Paxos/Raft)的另一版,而是为 主备复制 + 原子广播 的 ZooKeeper 架构量身定制:
- 提供 全序广播(Total Order Broadcast),确保所有非故障副本以相同顺序应用更新;
- 提供 崩溃恢复,在主机失败后能确定新的主,并将集群推进到一个一致的前缀;
- 提供 线性化写 的外部可观察性质,以及配合 API 的 顺序一致读 /
sync强化读。
核心思想:先恢复(确保新主携带“最新前缀”),再广播(全序提交)。
2. ZAB 是什么:目标、角色与术语
2.1 目标
- 安全性(Safety):不会出现两个不同提交序列。
- 活性(Liveness):只要多数派能互通,系统就能继续对外提供写服务。
- 全序(Total Order):所有提交的事务以相同顺序被所有副本应用。
2.2 角色
- Leader:唯一的主;分配事务 ID(ZXID)、发起并驱动广播。
- Follower:参与投票与复制;应用提交的事务。
- Observer:仅拉取数据,不参与投票(提升读扩展与跨机房就近读)。
2.3 关键术语
- Epoch(时代):领导任期标识;每次产生新 Leader,
epoch递增。 - ZXID:64 位事务 ID,高 32 位为
epoch,低 32 位为该任期内的递增计数。 - Proposal:事务提案(待提交日志条目)。
- Commit:事务提交广播(所有副本按序应用)。
- Quorum(多数派):
N个投票副本中必须 ≥⌊N/2⌋ + 1同意。
3. ZAB 的两种工作模式
ZAB 分为两个阶段性模式:
- Recovery(恢复):解决“谁是主 + 主从状态如何对齐”的问题;
- Broadcast(广播):在稳定领导下,提供按 ZXID 全序提交的写服务。
4. 领导者选举(Fast Leader Election)与主从确立
ZK 中的领导者选举并非 ZAB 的本文核心,但它决定了 epoch 的递增以及后续恢复过程。常见实现为 Fast Leader Election(FLE):
4.1 选票比较规则(简要)
- epoch(逻辑时钟/任期) 高者优先;
- ZXID 更新者优先(日志更“新”的候选者更可能当选);
- 服务器 ID(myid) 作为最终 tie-breaker。
4.2 选举交互(示意)
一旦多数派承认同一候选者,形成一个新的 Leader,并开启新的
epoch。
5. 日志与 ZXID:全序的来源
flowchart LR
subgraph ZXID[ZXID 结构:64 位]
E[高 32 位: epoch] -->|拼接| C[低 32 位: 该 epoch 内的计数器]
end
- 全序:同一
epoch内按低位计数器递增;不同epoch之间按epoch值全序。 - 单调性:新的 Leader 必须保证其日志前缀是多数派中“最新”的那个前缀(详见下一节恢复)。
6. 恢复阶段详解:Discovery / Synchronization
Leader 当选后不会立刻对外提供写服务,而是先让集群进入一致的前缀。
6.1 Discovery(发现)
- Leader 收集多数派的 最近 ZXID 与 历史承诺状态;
- Leader 选择一个比所有追随者更大的
epoch,并 宣告新的 epoch(NEWLEADER提案)。
6.2 Synchronization(同步)
-
Leader 计算每个 Follower 与自己日志的 差异:
- DIFF:Follower 仅缺少一段后缀 → 补齐缺失日志;
- TRUNC:Follower 出现分叉 → 回滚到共同前缀;
- SNAP:差异过大 → 直接下发快照。
-
当多数派 接受并应用
NEWLEADER且 与 Leader 对齐 后,集群切换到 Broadcast 模式。
只有当 多数派已与 Leader 同步 后,Leader 才会对外开放写请求。
7. 广播阶段详解:Proposal → Ack → Commit
在 Broadcast 模式中,所有写操作 都被封装为有序的 Proposal,并按如下流程提交:
要点:
- 多数派 Ack 才能 Commit:写入 WAL 后返回 ACK;
- 按 ZXID 全序提交:Follower 按收到的 COMMIT 顺序应用到状态机;
- Leader 驱动:只有 Leader 分配 ZXID 并推进提交序列。
8. 客户端读写路径与一致性保证
8.1 写入(线性化)
- 客户端向任一可用服务器发起写;若连接到 Follower,会被转发给 Leader;
- 一旦多数派 Ack 并收到 Commit,写操作对外可见(Linearizable Writes)。
8.2 读取(顺序一致 + 可选线性化)
-
默认:客户端从当前连接的副本读取,满足 顺序一致性(Sequential Consistency) 与 单系统映像(SSI):
- 同一客户端的操作按发送顺序被观察;
- 从不同服务器读取到的命名空间一致(最终收敛)。
-
需要“最新读”:调用
sync()或使用 读自 Leader 的模式,提高“新鲜度”。
8.3 典型读写路径示意
flowchart LR
C[Client] -->|write RPC| S[Connected Server]
S -->|forward| L[Leader]
L --> Q[Quorum Replication]
Q --> L
L -->|commit| S
S -->|success| C
C2[Client] -->|read RPC| S2[Connected Server]
S2 -->|直接读(可能稍滞后)| C2
C2 -->|sync()| S2
S2 --> L
L --> S2
S2 -->|线性化读| C2
9. 会话、心跳、临时节点与 Watch 机制
9.1 会话与心跳
- 客户端与服务器建立 会话(Session),周期性心跳;
tickTime与sessionTimeout影响过期判定;- 会话过期 → 释放该会话创建的 临时节点(Ephemeral)。
9.2 临时节点(Ephemeral)
- 与会话绑定,会话失效即自动删除;
- 不能有子节点(但可创建 临时顺序 节点用于选主/锁)。
9.3 Watch 机制
- 一次性触发(one-shot),触发后需重新注册;
- 事件按 全序提交 后异步通知客户端;
- 典型用途:配置变更感知、服务注册发现(/services/*)。
10. 容错与脑裂防护:多数派、Epoch 封印与隔离
- 多数派规则:只有获得多数派确认的提案才可能被提交;
- Epoch 封印:每次恢复开启新
epoch,旧 Leader 即使“复活”,也不能在新epoch中继续提交(被 fence 掉); - 网络分区:少数派那侧 不具备多数派,因此无法提交新事务 → 避免脑裂下多主提交。
flowchart TB
subgraph 分区1[分区 A (多数派)]
LA[Leader A(epoch k)] --> 提交
end
subgraph 分区2[分区 B (少数派)]
FB[Follower B] -->|无多数派| 停止提交
end
11. 性能工程:批量、流水线、磁盘落盘与快照
- 批量提案:Leader 可将短时内积累的请求打包,提高吞吐;
- 流水线(Pipelining):在上一个提案等待 ACK 时继续发送下一个;
- WAL + fsync:写前日志确保崩溃恢复;磁盘
fsync延迟是关键瓶颈; - 快照(Snapshot):周期性生成,配合日志截断,降低恢复与同步成本;
- Observer 横向扩读:不参与投票,扩展读能力并降低写路径压力。
12. 典型故障场景推演(附时序图)
12.1 Leader 崩溃
12.2 Follower 日志分叉
13. 与 Raft 的对比:相同点、差异与选择建议
相同点:
- 多数派提交、Leader 驱动、日志复制、按序提交、崩溃恢复。
差异点(工程视角):
- 模型取向:ZAB 更像 原子广播协议 + 主备;Raft 是 通用 SMR 共识协议。
- 恢复阶段:ZAB 明确划分 Discovery/Sync → Broadcast;Raft 以 任期 + 日志匹配 统一推进。
- 生态与用途:ZAB 强绑定 ZooKeeper;Raft 被广泛用于 Etcd/TiKV/CockroachDB 等。
选择建议:
- 需要 层次化命名空间 + Watch + 临时节点 + 简单 KV/元数据:ZooKeeper/ZAB;
- 需要 可嵌入式强一致 KV、广泛语言支持、通用 SMR:选 Raft 实现(如 Etcd/Raft 库)。
14. 集群部署与运维实践 Checklist
- 副本数:建议 3 或 5(奇数),避免 2(无法形成多数派)。
- 磁盘:优先本地 SSD;保证
fsync稳定;独立日志盘更佳。 - 网络:专线或高质量内网;避免单点交换机;多网卡绑定。
- JVM/内存:合适的堆/直内存;观察 GC 延迟;
maxClientCnxns控制每 IP 连接数。 - 参数:
tickTime、initLimit、syncLimit、autopurge.snapRetainCount、autopurge.purgeInterval; - 拓扑:跨机房部署时确保 一个机房能容纳多数派;Observer 放在远端机房。
- 备份:定期快照;开启自动清理旧日志与快照;
- 监控:延迟、吞吐、请求队列长度、过期会话数、fsync 时间、选举次数。
15. 常见误区与最佳实践
-
误区:ZooKeeper 的读是强一致。→ 更准确:写是线性化;读默认是顺序一致;需要“最新读”请使用
sync()或读 Leader。 -
误区:Observer 也能投票。→ 错误:Observer 仅同步数据,不参与投票与提交。
-
误区:临时节点可以有子节点。→ 错误:临时节点不允许有子节点(但可用于顺序节点)。
-
最佳实践:
- 用 临时顺序节点 实现选主/锁;
- Watch 是一次性触发,务必在回调中 尽早重新注册;
- 控制 单请求大小,避免一次性大节点写入拖垮
fsync; - 高并发写入场景开启 批量/流水线 并评估磁盘能力;
- 异地多活时远端使用 Observer,避免跨地域投票抖动。
16. 总结
ZAB 的核心可以用一句话概括:
新主先把多数派带到同一前缀(Recovery),再用 ZXID 全序广播提交(Broadcast)。
它以工程化的方式为 ZooKeeper 提供 线性化写 + 全序广播 + 高可用恢复 的能力,成为上层分布式协调(选主、配置、锁、服务发现)的可靠基石。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120782

浙公网安备 33010602011771号