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🌺点点关注,收藏不迷路🌺
© 版权声明

相关文章