ZooKeeper ZAB 协议阶段深度解析:从选举到同步的完整流程
ZooKeeper ZAB 协议阶段深度解析:从选举到同步的完整流程
-
- 一、ZAB 协议概述
-
- 1.1 什么是 ZAB 协议?
- 1.2 ZAB 协议的三个核心阶段
- 二、阶段一:选举阶段(Leader Election)
-
- 2.1 选举触发条件
- 2.2 选举投票规则
- 2.3 选举流程详解
- 2.4 选举阶段源码实现
- 三、阶段二:发现阶段(Discovery)
-
- 3.1 发现阶段的目标
- 3.2 发现阶段流程
- 3.3 发现阶段的源码实现
- 四、阶段三:同步阶段(Synchronization)
-
- 4.1 四种同步策略
- 4.2 四种同步方式详解
- 4.3 DIFF 同步流程
- 4.4 SNAP 全量同步
- 4.5 TRUNC 截断同步
- 五、阶段四:广播阶段(Broadcast)
-
- 5.1 广播阶段流程
- 5.2 广播阶段的顺序保证
- 六、阶段的完整生命周期
-
- 6.1 阶段转换图
- 6.2 各阶段的输入输出
- 七、源码实现中的阶段管理
-
- 7.1 节点状态定义
- 7.2 QuorumPeer 的状态机
- 八、实践验证
-
- 8.1 观察阶段转换
- 8.2 日志示例
- 九、总结
-
- 9.1 四个阶段的核心要点
- 9.2 阶段间的数据流动
- 9.3 一句话总结
|
🌺The Begin🌺点点关注,收藏不迷路🌺
|
摘要:ZAB(ZooKeeper Atomic Broadcast)协议是 ZooKeeper 保证分布式数据一致性的核心。它不仅包含了消息广播和崩溃恢复两大模式,更细分为多个精密的阶段。本文将深入剖析 ZAB 协议的完整生命周期,包括选举阶段、发现阶段、同步阶段和广播阶段,通过流程图和源码分析,帮助读者全面理解 ZooKeeper 如何在不同阶段确保数据一致性。
一、ZAB 协议概述
1.1 什么是 ZAB 协议?
ZAB 协议是 ZooKeeper 专门设计的一种支持崩溃恢复的原子广播协议。它将分布式系统的复杂问题分解为多个有序的阶段,确保在任何情况下数据的一致性和可靠性。
ZAB 协议完整生命周期
是
否
集群启动
选举阶段
Leader Election
发现阶段
Discovery
同步阶段
Synchronization
广播阶段
Broadcast
Leader 故障?
1.2 ZAB 协议的三个核心阶段
根据 ZooKeeper 的官方文档和源码实现,ZAB 协议主要包含三个核心阶段:
| 阶段 | 英文名称 | 主要任务 | 输入 | 输出 |
|---|---|---|---|---|
| 选举阶段 | Leader Election | 选出新 Leader | 节点投票 (myid, lastZxid) | 新 Leader 产生 |
| 恢复阶段 | Recovery | 数据同步 | 各节点 lastZxid | 所有节点数据一致 |
| 广播阶段 | Broadcast | 处理写请求 | 客户端写操作 | 事务提交 |
二、阶段一:选举阶段(Leader Election)
选举阶段是 ZAB 协议的起点,当集群启动或 Leader 故障时,所有节点进入 LOOKING 状态,开始选举过程。
2.1 选举触发条件
集群首次启动
Leader 心跳超时
Leader 与多数派失联
正常运行
节点状态检测
是否满足触发条件?
进入选举阶段
保持广播阶段
2.2 选举投票规则
每个节点在选举阶段会广播投票信息 (myid, lastZxid),投票比较规则如下:
public class VoteComparison {
/**
* 投票比较规则
* @return true 表示对方投票更优,应该更新自己的投票
*/
public boolean isOtherVoteBetter(Vote myVote, Vote otherVote) {
// 规则1:优先比较 lastZxid
if (otherVote.lastZxid > myVote.lastZxid) {
return true; // 对方数据更新
}
if (otherVote.lastZxid < myVote.lastZxid) {
return false; // 自己数据更新
}
// 规则2:lastZxid 相同时比较 myid
return otherVote.myid > myVote.myid;
}
}
2.3 选举流程详解
节点4 (myid=4, zxid=95)
节点3 (myid=3, zxid=90)
节点2 (myid=2, zxid=100)
节点1 (myid=1, zxid=100)
节点4 (myid=4, zxid=95)
节点3 (myid=3, zxid=90)
节点2 (myid=2, zxid=100)
节点1 (myid=1, zxid=100)
所有节点进入 LOOKING 状态
比较过程
收到(2,100),优于自己(1,100)
更新投票为(2,100)
收到(1,100),不如自己(2,100)
保持投票(2,100)
收到(2,100),优于自己(3,90)
更新投票为(2,100)
收到(2,100),优于自己(4,95)
更新投票为(2,100)
统计得票:获得4票
超过半数(5节点需3票)
投票(1,100)
投票(1,100)
投票(1,100)
投票(2,100)
投票(2,100)
投票(2,100)
投票(3,90)
投票(3,90)
投票(3,90)
投票(4,95)
投票(4,95)
投票(4,95)
状态变更为 LEADING
通知选举结果
通知选举结果
通知选举结果
状态变更为 FOLLOWING
状态变更为 FOLLOWING
状态变更为 FOLLOWING
2.4 选举阶段源码实现
// FastLeaderElection.java - 选举核心逻辑
public class FastLeaderElection {
public Vote lookForLeader() throws InterruptedException {
// 1. 更新逻辑时钟
logicalclock.incrementAndGet();
// 2. 初始化投票给自己
updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
// 3. 发送投票
sendNotifications();
// 4. 循环接收投票,直到选出 Leader
while (running) {
Notification n = recvqueue.poll(notTimeout, TimeUnit.MILLISECONDS);
if (n == null) {
// 超时重发
sendNotifications();
continue;
}
// 5. 处理收到的投票
if (n.electionEpoch > logicalclock.get()) {
logicalclock.set(n.electionEpoch);
updateProposal(n);
} else if (n.electionEpoch < logicalclock.get()) {
continue;
}
// 6. 更新投票(比较规则)
if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
proposedLeader, proposedZxid, proposedEpoch)) {
updateProposal(n);
sendNotifications();
}
// 7. 统计得票
Set<Vote> votes = new HashSet<>();
votes.add(new Vote(proposedLeader, proposedZxid));
// 8. 判断是否达到过半
if (voteSet.hasAllQuorums()) {
return new Vote(proposedLeader, proposedZxid);
}
}
return null;
}
}
三、阶段二:发现阶段(Discovery)
Leader 选举完成后,进入发现阶段。新 Leader 需要收集所有 Follower 的元数据信息,为后续的数据同步做准备。
3.1 发现阶段的目标
| 目标 | 说明 |
|---|---|
| 收集 Follower 的 lastZxid | 了解每个 Follower 的数据状态 |
| 确定新的 epoch | 新 Leader 开始新的任期 |
| 建立初始同步计划 | 为同步阶段准备数据 |
3.2 发现阶段流程
Follower3
Follower2
Follower1
新 Leader
Follower3
Follower2
Follower1
新 Leader
进入 LEADING 状态
4. 收集所有 lastZxid
5. 确定 minZxid 和 maxZxid
6. 准备进入同步阶段
1. LEADERINFO(newEpoch)
1. LEADERINFO(newEpoch)
1. LEADERINFO(newEpoch)
2. 记录新 epoch
2. 记录新 epoch
2. 记录新 epoch
3. ACKEPOCH(lastZxid)
3. ACKEPOCH(lastZxid)
3. ACKEPOCH(lastZxid)
3.3 发现阶段的源码实现
// Leader.java - 处理 Follower 的 ACKEPOCH
public class Leader {
private Map<Long, Long> followerLastZxid = new ConcurrentHashMap<>();
public void processAckEpoch(QuorumPacket qp, LearnerHandler handler) {
long lastZxid = qp.getZxid();
long sid = handler.getSid();
// 记录 Follower 的 lastZxid
followerLastZxid.put(sid, lastZxid);
// 更新全局最小和最大 ZXID
updateZxidRange(lastZxid);
// 如果收到过半 ACK,准备进入下一阶段
if (followerLastZxid.size() > half) {
// 进入同步阶段
startSynchronization();
}
}
}
四、阶段三:同步阶段(Synchronization)
同步阶段的核心任务是将所有节点的数据与 Leader 对齐,确保数据一致性。
4.1 四种同步策略
Leader 根据每个 Follower 上报的 lastZxid,决定采用哪种同步方式:
等于 Leader.maxZxid
小于 Leader.minZxid
在 Leader.minZxid 和 maxZxid 之间
大于 Leader.maxZxid
收到 Follower 的 lastZxid
比较 lastZxid
无需同步
SNAP 全量同步
DIFF 增量同步
TRUNC 截断同步
Leader 发送完整快照
Leader 发送缺失事务
Follower 回滚多余事务
同步完成
4.2 四种同步方式详解
| 同步方式 | 触发条件 | 操作 | 适用场景 |
|---|---|---|---|
| DIFF | minZxid ≤ lastZxid ≤ maxZxid |
发送缺失的事务 | 节点短暂故障后恢复 |
| SNAP | lastZxid < minZxid |
发送完整快照 | 新节点加入,或长时间故障 |
| TRUNC | lastZxid > maxZxid |
回滚多余事务 | 旧 Leader 恢复后加入 |
| TRUNC+DIFF | 需要先回滚再同步 | 先 TRUNC 再 DIFF | 复杂故障场景 |
4.3 DIFF 同步流程
Leader (min=90, max=120)
Follower (lastZxid=100)
Leader (min=90, max=120)
Follower (lastZxid=100)
1. DIFF 指令
2. PROPOSAL(zxid=101)
2. PROPOSAL(zxid=102)
2. PROPOSAL(zxid=103)
3. 写入事务日志
4. COMMIT(zxid=101)
4. COMMIT(zxid=102)
4. COMMIT(zxid=103)
5. 应用到内存
4.4 SNAP 全量同步
// LearnerHandler.java - 处理 SNAP 同步
public class LearnerHandler {
private void handleSnapSync() {
// 1. 获取内存数据快照
byte[] snapshot = zk.getZKDatabase().getSnapshot();
// 2. 发送 SNAP 指令
QuorumPacket snapPacket = new QuorumPacket(Leader.SNAP,
lastZxid,
snapshot,
null);
queuePacket(snapPacket);
// 3. 发送 UPTODATE 确认
queuePacket(new QuorumPacket(Leader.UPTODATE, -1, null, null));
}
}
4.5 TRUNC 截断同步
# TRUNC 同步的场景
# Follower 有 Leader 没有的事务(曾是旧 Leader)
# Follower 当前日志
log.100000001 ~ log.100000010 # 旧 Leader 任期
log.200000001 ~ log.200000005 # 新 Leader 任期,但有额外事务
# Leader 发送 TRUNC 指令后
# Follower 回滚到 log.200000005,删除多余事务
五、阶段四:广播阶段(Broadcast)
完成数据同步后,集群进入广播阶段,也就是正常服务阶段。在这个阶段,Leader 负责处理所有写请求。
5.1 广播阶段流程
Follower3
Follower2
Follower1
Leader
客户端
Follower3
Follower2
Follower1
Leader
客户端
4. 收到过半 ACK
写请求
1. 生成 Proposal,分配 ZXID
2. PROPOSAL(ZXID)
2. PROPOSAL(ZXID)
2. PROPOSAL(ZXID)
3. ACK
3. ACK
5. COMMIT
5. COMMIT
5. COMMIT
6. 返回成功
5.2 广播阶段的顺序保证
// Leader 为每个 Follower 维护 FIFO 队列
public class Leader {
class LearnerHandler {
private LinkedBlockingQueue<QuorumPacket> queuedPackets =
new LinkedBlockingQueue<>();
public void queuePacket(QuorumPacket p) {
// 按顺序放入队列
queuedPackets.offer(p);
}
public void run() {
while (running) {
// 按顺序取出发送
QuorumPacket p = queuedPackets.take();
sendPacket(p);
}
}
}
}
六、阶段的完整生命周期
6.1 阶段转换图
选举阶段:集群启动/Leader故障
选举阶段
发现阶段:新Leader产生
发现阶段
同步阶段:收集完lastZxid
DIFF
SNAP
TRUNC
广播阶段:所有节点数据对齐
广播阶段
选举阶段:Leader故障
广播阶段:处理新请求
6.2 各阶段的输入输出
| 阶段 | 输入 | 处理 | 输出 | 时间开销 |
|---|---|---|---|---|
| 选举阶段 | 节点启动/Leader故障 | 投票比较 | 新 Leader | 秒级 |
| 发现阶段 | Leader 身份 | 收集 lastZxid | 同步计划 | 毫秒级 |
| 同步阶段 | lastZxid 差异 | 数据对齐 | 一致的数据 | 取决于落后程度 |
| 广播阶段 | 客户端请求 | 事务广播 | 数据更新 | 毫秒级 |
七、源码实现中的阶段管理
7.1 节点状态定义
// ServerState.java - 节点状态枚举
public enum ServerState {
LOOKING, // 选举阶段
FOLLOWING, // 同步阶段/广播阶段(作为 Follower)
LEADING, // 同步阶段/广播阶段(作为 Leader)
OBSERVING // Observer 节点
}
7.2 QuorumPeer 的状态机
// QuorumPeer.java - 主状态机
public class QuorumPeer extends ZooKeeperThread {
public void run() {
while (running) {
switch (getPeerState()) {
case LOOKING:
// 执行选举阶段
setCurrentVote(makeLEStrategy().lookForLeader());
break;
case FOLLOWING:
// 作为 Follower 执行发现、同步、广播
follower.followLeader();
break;
case LEADING:
// 作为 Leader 执行发现、同步、广播
leader.lead();
break;
}
}
}
}
八、实践验证
8.1 观察阶段转换
# 1. 查看节点状态
./bin/zkServer.sh status
# Mode: leader 或 follower
# 2. 模拟 Leader 故障
kill -9 <leader_pid>
# 3. 观察其他节点状态变化
watch -n 1 './bin/zkServer.sh status'
# 4. 查看日志中的阶段转换
tail -f logs/zookeeper.out | grep -E "LOOKING|LEADING|FOLLOWING"
8.2 日志示例
2024-01-01 10:00:00 - Node entering LOOKING state # 进入选举阶段
2024-01-01 10:00:01 - Node received vote from 2, updating vote to (2,100)
2024-01-01 10:00:02 - Node elected as Leader # 选举完成
2024-01-01 10:00:03 - Starting discovery phase # 进入发现阶段
2024-01-01 10:00:03 - Received ACKEPOCH from 3 followers
2024-01-01 10:00:04 - Starting synchronization # 进入同步阶段
2024-01-01 10:00:04 - Using DIFF sync for node 2
2024-01-01 10:00:05 - Node entered broadcast phase # 进入广播阶段
九、总结
9.1 四个阶段的核心要点
| 阶段 | 英文名称 | 核心任务 | 关键机制 |
|---|---|---|---|
| 选举阶段 | Election | 选出新 Leader | 基于 ZXID 和 myid 的投票比较 |
| 发现阶段 | Discovery | 收集元数据 | 确定新 epoch,收集 lastZxid |
| 同步阶段 | Synchronization | 数据对齐 | DIFF/SNAP/TRUNC 四种策略 |
| 广播阶段 | Broadcast | 处理写请求 | 两阶段广播,过半确认 |
9.2 阶段间的数据流动
ZAB 协议数据流
新 Leader
lastZxid 集合
一致的数据
事务请求
更新 lastZxid
Leader 故障
选举阶段
发现阶段
同步阶段
广播阶段
新事务产生
9.3 一句话总结
ZAB 协议通过选举阶段确定 Leader,发现阶段收集元数据,同步阶段对齐数据,广播阶段处理事务,四个阶段环环相扣、循环往复,共同构建了 ZooKeeper 高可用、强一致性的分布式协调基础。

|
🌺The End🌺点点关注,收藏不迷路🌺
|