本章定位:ZAB 协议是 ZooKeeper 集群一致性的核心引擎。理解 ZAB 的广播模式和恢复模式,才能真正掌握 ZooKeeper 的数据同步原理。
定义与作用
ZooKeeper Atomic Broadcast(ZAB) 是 ZooKeeper 自研的一致性协议,用于在集群节点间可靠地复制状态变更。
ZAB 协议解决的分布式痛点:
| 痛点 | ZAB 的解决方案 |
|---|---|
| 多副本数据一致 | 原子广播:事务要么全部节点执行,要么都不执行 |
| Leader 故障 | 崩溃恢复:自动选举新 Leader 并回滚未提交事务 |
| 网络分区 | Quorum 机制:只有多数派存活时继续服务 |
| 消息顺序性 | FIFO 通道 + ZXID 单调递增保证全局顺序 |
核心原理
两种运行模式
ZAB 协议在两种模式下交替运行:
消息广播模式
广播流程:Leader 提案 → Follower 确认 → Leader 收集多数 ACK → 广播 Commit。只有过半数 Follower ACK 后才会提交,保证一致性。
ZXID 设计
ZXID(ZooKeeper Transaction ID)是 ZAB 协议的核心设计:
ZXID: 64-bit
┌───────────────┬───────────────┐
│ epoch (32bit) │ counter (32bit) │
└───────────────┴───────────────┘
| 字段 | 含义 | 变化时机 |
|---|---|---|
| epoch | 任期号 | Leader 变更时 +1 |
| counter | 事务计数器 | 每次写操作 +1 |
ZXID 全局单调递增,是崩溃恢复时比较事务新旧的依据。
完整示例
示例一:追踪 ZXID 变化
场景说明:通过 zkCli.sh 观察每次写操作的 ZXID 递增。
zkCli.sh -server 127.0.0.1:2181
# 创建节点,观察 czxid 和 mzxid
[zkshell: 0] create /track "step-1"
Created /track
# czxid = mzxid = 0x100000001
[zkshell: 1] set /track "step-2"
# mzxid = 0x100000002(counter 从 1 → 2)
[zkshell: 2] set /track "step-3"
# mzxid = 0x100000003(counter 从 2 → 3)
[zkshell: 3] create /track/child "c1"
# czxid = 0x100000004
# 父节点 pzxid = 0x100000004(最后一个修改子节点的事务)
操作前后对比:
| 操作 | ZXID | epoch | counter | 影响 |
|---|---|---|---|---|
| create /track | 0x100000001 | 0x1 | 1 | czxid=1, mzxid=1 |
| set step-2 | 0x100000002 | 0x1 | 2 | mzxid=2, version=1 |
| set step-3 | 0x100000003 | 0x1 | 3 | mzxid=3, version=2 |
| create child | 0x100000004 | 0x1 | 4 | pzxid=4, cversion=1 |
示例二:观察崩溃恢复后 epoch 变化
场景说明:集群 Leader 宕机后新 Leader epoch 递增。
# Leader(2182 端口)宕机前
zkCli.sh -server 127.0.0.1:2182
[zkshell: 0] get /test
# mzxid = 0x100000010
# 停掉 Leader
bin/zkServer.sh stop conf/zoo-2.cfg
# 等待选举完成(约 10 秒)
# 新 Leader(2181 端口)
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] create /after-election "new data"
Created /after-election
# czxid = 0x200000001(epoch 从 0x1 → 0x2)
操作前后对比:
| 时间点 | 当前 Leader | 最新 ZXID | epoch |
|---|---|---|---|
| Leader 宕机前 | Server 2 (2182) | 0x100000010 | 0x1 |
| Leader 宕机后 | Server 1 (2181) | 0x200000001 | 0x2(递进) |
示例三:Java API 验证原子广播
场景说明:并发写入多个节点,验证事务的原子性和顺序性。
public class ZABDemo {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
// 3 个客户端同时写入
for (int i = 0; i < 3; i++) {
final int idx = i;
new Thread(() -> {
try {
ZooKeeper zk = new ZooKeeper(
"127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183",
3000, e -> {});
latch.await(); // 同时释放
Stat stat = new Stat();
zk.getData("/zab-test", false, stat);
// 乐观锁更新
zk.setData("/zab-test",
("client-" + idx + "-" + stat.getVersion())
.getBytes(),
stat.getVersion());
zk.close();
} catch (Exception e) {
System.out.println("Client " + idx + " failed: "
+ e.getMessage());
}
}).start();
}
latch.countDown();
Thread.sleep(5000);
// 最终值由 Leader 串行化决定
ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, e -> {});
System.out.println("Final: " +
new String(zk.getData("/zab-test", false, null)));
zk.close();
}
}
操作前后对比:
| 阶段 | 说明 |
|---|---|
| 操作前 | /zab-test 创建,version=0 |
| 3 个客户端并发 setData | 第一个成功的将 version 推进到 1,其余因 version 冲突失败或重试 |
| 操作后 | 只有一个客户端成功写入,数据一致 |
ZAB 协议保证:所有写请求在 Leader 端串行化成 Proposal,按 ZXID 顺序广播。并发请求最终被序列化为全局唯一顺序。
易错场景与面试考点
易错场景
1. 混淆 ZAB 和 Paxos 的 Quorum 概念
ZAB 的 Quorum 是过半数节点成功执行事务,而非 Paxos 的过半数接受提案。ZAB 要求已提交的事务必须被 Quorum 持久化,崩溃恢复时未提交的事务会被回滚。
2. 误解 ZAB 的提交语义
ZAB 采用主备复制模型(Primary-Backup),而非 Multi-Paxos 的全对等模型。Leader 是唯一的主节点,Follower 被动同步。这在实现上简化了状态管理。
3. 认为 ZAB 等于 Paxos
ZAB 不是 Paxos 的直接实现,而是专门为 ZooKeeper 设计的协议。核心区别:
- ZAB 使用主备模式,Paxos 使用对等节点
- ZAB 的崩溃恢复有明确的 Leader 激活期
- ZAB 保证 FIFO 顺序(按 ZXID),Paxos 不保证
面试高频题
Q:ZAB 协议与 Raft 协议的主要异同?
A:
- 相似:Leader-based、日志复制、Quorum 投票、任期(epoch/term)
- 不同:ZAB 使用 FIFO 通道 + ZXID,Raft 使用 log index + term;ZAB 的提交不要求日志连续(可乱序到达),Raft 要求严格连续
Q:崩溃恢复时 ZAB 如何保证状态一致?
A:新 Leader 执行以下步骤:
- 收集所有 Follower 的最近 ZXID
- 确定最大已提交 ZXID(至少一个 Quorum 的 Follower 有此 ZXID)
- 通知所有 Follower 截断(Truncate)到该 ZXID
- Leader 向 Follower 同步(Snapshot + Diff)到最新状态
Q:ZXID 为何采用 epoch + counter 设计?
A:epoch 解决了 Leader 变更后的序号冲突——即使新 Leader 重置 counter 为 0,由于 epoch 递增,ZXID 仍然全局唯一且递增。同时 ZXID 可直接比较新旧(不需要额外的 term 序列化逻辑)。
小结
| 要点 | 说明 |
|---|---|
| 两种模式 | 消息广播(正常运行)+ 崩溃恢复(Leader 变更) |
| 两阶段提交 | Proposal → ACK(Quorum) → Commit |
| ZXID | epoch(32bit) + counter(32bit),全局单调递增 |
| FIFO 通道 | 消息按 ZXID 顺序有序传递 |
| Quorum | 必须过半数节点参与才能提交事务 |
ZAB 协议是 ZooKeeper 一致性的基石。下一节深入"一致性保证",从客户端角度理解 ZAB 提供的一致性语义。