面试知识库
困难

Zookeeper-ZAB协议#

一句话答案#

ZAB 是 Zookeeper 的原子广播协议,两种模式:崩溃恢复(选主+数据同步)和消息广播(过半确认提交)。

核心要点

ZAB(ZooKeeper Atomic Broadcast)协议: ZooKeeper 使用的原子广播协议,保证分布式数据的一致性。

ZAB 的两种模式:

ZAB vs Raft 对比:

维度ZABRaft
设计目标高可用的顺序广播协议(ZooKeeper 专用)通用分布式共识算法
Leader 选举选拥有最大 ZXID 的节点随机超时 + 多数投票(日志最新者优先)
任期标识epoch(纪元)term(任期)
日志标识ZXID(epoch + counter 组合的 64 位 ID)term + logIndex
数据同步恢复模式有专门的同步阶段(DIFF / TRUNC / SNAP)通过 AppendEntries 逐步对齐
模式切分显式分为恢复模式和广播模式无显式模式切分,选举和日志复制无缝衔接
读请求默认 Follower 可读(可能读到旧数据),可通过 sync 强制读 Leader默认读 Leader(强一致)

相同点:

  • 都是 Leader-based 的共识协议(单 Leader 负责写入)
  • 都需要多数节点(Quorum)确认才能提交
  • 都保证已提交的日志不会丢失
  • 都通过心跳机制检测 Leader 存活

关键差异总结: ZAB 是为 ZooKeeper 量身定制的,侧重于主备模式下的原子广播和崩溃恢复;Raft 是通用共识算法,更强调可理解性和工程友好性。从实现角度看,ZAB 的恢复阶段更复杂(需要处理多种同步场景),而 Raft 通过简单的日志匹配机制统一处理。

FastLeaderElection 选举算法#

「选 ZXID 最大」只是结论,真正的选举靠 FastLeaderElection(FLE) 通过反复投票 + PK 收敛。

ZXID 的结构: 64 位 = 高 32 位 epoch(选举纪元)+ 低 32 位 counter(该 epoch 内递增的事务序号)。比较 ZXID 即先比 epoch 再比 counter。

投票 PK 规则(依次比较,大者胜): 每张选票携带 (vote_epoch, vote_zxid, vote_myid),节点收到别人的票后与自己当前选票按下面顺序 PK:

1. 先比 electionEpoch(选举轮次/logicalclock)—— 轮次大的更新,直接采纳
2. epoch 相同 → 比 ZXID(事务 id)—— ZXID 大者数据更新,胜
3. ZXID 也相同 → 比 myid(服务器配置 id)—— myid 大者胜(仅用于打破平局)
plaintext

收敛过程: 每个节点初始投自己;收到一票就按上述规则 PK,若对方选票更优则更新自己的选票并重新广播,否则坚持原票回广播。如此一轮轮 PK,所有节点的选票会快速收敛到「数据最新」的那个节点。

票箱过半判定: 节点统计自己票箱里,投给「当前领先候选者」的票数,一旦超过半数> N/2)即认定其当选,自己转为 LOOKING→FOLLOWING/LEADING。过半机制保证最多只有一个 Leader 当选。

为何「ZXID 最大」能保证不丢已提交数据#

这是 ZAB 安全性的核心,本质是**「过半提交」+「过半选举」两个多数派必然相交**:

  • 一条提案被 commit 的前提是 Leader 收到了过半 Follower 的 ACK,即该事务已复制到一个多数派集合 A
  • 选举要求候选者拿到过半选票,参与投票的也是一个多数派集合 B
  • 任意两个多数派必有交集(鸽巢原理),所以 A ∩ B ≠ ∅至少有一个既持有该已提交事务、又参与了选举的节点
  • 由于 FLE 选的是 ZXID 最大者,这个交集节点的 ZXID ≥ 该已提交事务的 ZXID,因此选出的新 Leader 的 ZXID 一定 ≥ 任何已提交事务 → 已 commit 的数据不可能丢失

反向理解:一个没被复制到多数派的事务(未 commit),可能在新 Leader 上被 TRUNC 截断丢弃,这是允许的——ZAB 只承诺「已提交不丢」。

epoch 递增与防脑裂#

恢复阶段 epoch 自增: 新 Leader 选出后进入发现阶段,会取所有 Follower 见过的最大 epoch 加 1 作为新 epoch(newEpoch = maxEpoch + 1),并要求过半 Follower 接受这个新 epoch,写入 acceptedEpoch。此后该任期产生的所有 ZXID 高位都是这个新 epoch。

为何能防旧 Leader 复活脑裂: 假设老 Leader 因网络分区被误判宕机,集群已选出新 Leader 并把 epoch 推到了更高值。等老 Leader 网络恢复回来,它仍用旧 epoch 发提案:

  • Follower 发现提案的 epoch 小于自己已接受的 acceptedEpoch,直接拒绝老 Leader 的提案和心跳。
  • 老 Leader 收不到过半 ACK,无法提交任何事务,被迫转回 LOOKING 重新参与选举,降级为 Follower。

epoch 单调递增因此充当「逻辑时钟 / 任期号」,保证旧任期的写入永远无法被多数派接受,从根上杜绝了「两个 Leader 同时提交」的脑裂。

面试回答(2分钟版)

ZAB是ZooKeeper专用的原子广播协议,保证分布式数据一致性,有两种工作模式。崩溃恢复模式在Leader挂掉或集群启动时触发,先选举出拥有最大ZXID的节点作为新Leader,然后新Leader将最新数据通过DIFF、TRUNC或SNAP方式同步给所有Follower,多数Follower确认同步完成后进入正常工作。消息广播模式处理客户端写请求,Leader生成Proposal并分配全局递增的ZXID,广播给所有Follower,Follower写入本地日志后返回ACK,Leader收到过半ACK就发送Commit,类似于一个简化的两阶段提交。和Raft对比,两者都是Leader-based的共识协议,都需要多数节点确认才能提交。核心区别在于ZAB是为ZooKeeper量身定制的,有专门的恢复阶段来处理多种同步场景,而Raft是通用共识算法更强调可理解性和工程友好性。另外ZK默认Follower可读可能读到旧数据属于CP系统,选举期间服务不可用。

追问与易错

追问方向:

  • “ZAB 和 Raft 核心区别?”→ ZAB 为 ZooKeeper 量身定制有专门的恢复阶段(DIFF/TRUNC/SNAP),Raft 是通用共识算法更强调可理解性;ZAB 用 epoch+ZXID,Raft 用 term+logIndex
  • “Leader 选举过程?”→ 每个节点投票给 ZXID 最大的节点(数据最新),获得过半票数的成为 Leader;同 ZXID 时比较 myid,选举期间集群不可用(CP 特性)
  • “ZK 适合做注册中心吗?”→ 可以但不是最优,ZK 是 CP 系统选举期间不可用,注册中心更需要 AP(高可用),Nacos/Eureka 在注册发现场景更合适

易错点:

  • ❌ ZK 能保证高可用——ZK 是 CP 选举期间不可用
  • ❌ ZAB 和 Paxos 完全一样——增加了崩溃恢复