文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

分布式系统【五、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 分为两个阶段性模式:

集群启动/主机故障
新 Leader 建立并完成同步
Leader 崩溃/通信分区
Recovery
Broadcast
  • Recovery(恢复):解决“谁是主 + 主从状态如何对齐”的问题;
  • Broadcast(广播):在稳定领导下,提供按 ZXID 全序提交的写服务。

4. 领导者选举(Fast Leader Election)与主从确立

ZK 中的领导者选举并非 ZAB 的本文核心,但它决定了 epoch 的递增以及后续恢复过程。常见实现为 Fast Leader Election(FLE)

4.1 选票比较规则(简要)

  1. epoch(逻辑时钟/任期) 高者优先;
  2. ZXID 更新者优先(日志更“新”的候选者更可能当选);
  3. 服务器 ID(myid) 作为最终 tie-breaker。

4.2 选举交互(示意)

Server1Server2Server3投票(提名 S1, epoch, zxid)投票(提名 S2, epoch, zxid)投票(提名 S3, epoch, zxid)通过反复交换选票,逐步收敛到同一候选者接受 S1 为 Leader接受 S1 为 Leader宣布当选宣布当选Server1Server2Server3

一旦多数派承认同一候选者,形成一个新的 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,并 宣告新的 epochNEWLEADER 提案)。

6.2 Synchronization(同步)

  • Leader 计算每个 Follower 与自己日志的 差异

    • DIFF:Follower 仅缺少一段后缀 → 补齐缺失日志;
    • TRUNC:Follower 出现分叉 → 回滚到共同前缀;
    • SNAP:差异过大 → 直接下发快照。
  • 当多数派 接受并应用 NEWLEADER与 Leader 对齐 后,集群切换到 Broadcast 模式。

新LeaderFollower AFollower BNEWLEADER(epoch=k)NEWLEADER(epoch=k)ACK NEWLEADER (携带自身ZXID)ACK NEWLEADER (携带自身ZXID)DIFF/TRUNC/SNAP (按需同步)DIFF/TRUNC/SNAP (按需同步)对齐完成(Ready)对齐完成(Ready)多数派对齐后进入 Broadcast新LeaderFollower AFollower B

只有当 多数派已与 Leader 同步 后,Leader 才会对外开放写请求。


7. 广播阶段详解:Proposal → Ack → Commit

在 Broadcast 模式中,所有写操作 都被封装为有序的 Proposal,并按如下流程提交:

ClientLeaderFollower1Follower2create/set/delete(...)分配ZXID(e,k)PROPOSAL(e,k)PROPOSAL(e,k)ACK(e,k)ACK(e,k)COMMIT(e,k)COMMIT(e,k)成功 (线性化写)ClientLeaderFollower1Follower2

要点:

  • 多数派 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),周期性心跳;
  • tickTimesessionTimeout 影响过期判定;
  • 会话过期 → 释放该会话创建的 临时节点(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 崩溃

Leader(epoch k)Follower1Follower2PROPOSAL(e,k,PROPOSAL(e,k,崩溃触发选举 (心跳超时)交换日志元信息(zxid)确认可成为新Leader形成新 Leader(epoch k+1) 并同步Leader(epoch k)Follower1Follower2

12.2 Follower 日志分叉

新LeaderFollower(旧分叉)NEWLEADER(epoch k)ACK (zxid 显示存在分叉)TRUNC 回滚到共同前缀DIFF 补齐正确后缀Ready(对齐完成)新LeaderFollower(旧分叉)

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 连接数。
  • 参数tickTimeinitLimitsyncLimitautopurge.snapRetainCountautopurge.purgeInterval
  • 拓扑:跨机房部署时确保 一个机房能容纳多数派;Observer 放在远端机房。
  • 备份:定期快照;开启自动清理旧日志与快照;
  • 监控:延迟、吞吐、请求队列长度、过期会话数、fsync 时间、选举次数。

15. 常见误区与最佳实践

  • 误区:ZooKeeper 的读是强一致。→ 更准确:写是线性化;读默认是顺序一致;需要“最新读”请使用 sync() 或读 Leader。

  • 误区:Observer 也能投票。→ 错误:Observer 仅同步数据,不参与投票与提交。

  • 误区:临时节点可以有子节点。→ 错误:临时节点不允许有子节点(但可用于顺序节点)。

  • 最佳实践

    • 临时顺序节点 实现选主/锁;
    • Watch 是一次性触发,务必在回调中 尽早重新注册
    • 控制 单请求大小,避免一次性大节点写入拖垮 fsync
    • 高并发写入场景开启 批量/流水线 并评估磁盘能力;
    • 异地多活时远端使用 Observer,避免跨地域投票抖动。

16. 总结

ZAB 的核心可以用一句话概括:

新主先把多数派带到同一前缀(Recovery),再用 ZXID 全序广播提交(Broadcast)

它以工程化的方式为 ZooKeeper 提供 线性化写 + 全序广播 + 高可用恢复 的能力,成为上层分布式协调(选主、配置、锁、服务发现)的可靠基石。

posted @ 2025-09-03 14:25  NeoLshu  阅读(5)  评论(0)    收藏  举报  来源