ZooKeeper ZAB 协议:选举与广播原理解析
作者:AI技术博客作者 标签:#ZooKeeper #ZAB #选举 #广播 #一致性协议 阅读时间:约 15 分钟 难度:进阶
目录
1. 引言:ZAB 协议概述
ZooKeeper Atomic Broadcast(ZAB)协议是 ZooKeeper 中保证数据一致性的核心原子广播协议。它专为 ZooKeeper 设计,旨在解决分布式协调服务中的数据一致性问题。
1.1 为什么需要 ZAB 协议?
在分布式系统中,多个节点需要协调工作并保持数据一致性。ZooKeeper 作为分布式协调服务,必须确保:
- 数据一致性:所有服务器看到的数据视图必须相同
- 高可用性:部分节点故障时系统仍能正常工作
- 顺序一致性:更新操作按照全局顺序执行
1.2 ZAB 协议的核心特性
| 原子广播 | 所有事务请求按相同顺序广播到所有副本 | 配置中心更新 |
| 故障恢复 | 领导者崩溃时自动选举新领导者 | 服务自动恢复 |
| 主从架构 | 单一领导者 + 多个跟随者 | 读多写少场景 |
2. ZAB 协议核心概念
2.1 服务器状态
ZAB 协议定义了服务器的四种状态:
#mermaid-svg-TT62EwK02578CAkS{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TT62EwK02578CAkS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TT62EwK02578CAkS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TT62EwK02578CAkS .error-icon{fill:#552222;}#mermaid-svg-TT62EwK02578CAkS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TT62EwK02578CAkS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TT62EwK02578CAkS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TT62EwK02578CAkS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TT62EwK02578CAkS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TT62EwK02578CAkS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TT62EwK02578CAkS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TT62EwK02578CAkS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TT62EwK02578CAkS .marker.cross{stroke:#333333;}#mermaid-svg-TT62EwK02578CAkS svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TT62EwK02578CAkS p{margin:0;}#mermaid-svg-TT62EwK02578CAkS defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-TT62EwK02578CAkS g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-TT62EwK02578CAkS g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-TT62EwK02578CAkS g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-TT62EwK02578CAkS g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-TT62EwK02578CAkS g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-TT62EwK02578CAkS .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-TT62EwK02578CAkS .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-TT62EwK02578CAkS .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-TT62EwK02578CAkS .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-TT62EwK02578CAkS .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-TT62EwK02578CAkS .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-TT62EwK02578CAkS .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-TT62EwK02578CAkS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TT62EwK02578CAkS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TT62EwK02578CAkS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TT62EwK02578CAkS .edgeLabel .label text{fill:#333;}#mermaid-svg-TT62EwK02578CAkS .label div .edgeLabel{color:#333;}#mermaid-svg-TT62EwK02578CAkS .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-TT62EwK02578CAkS .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-TT62EwK02578CAkS .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-TT62EwK02578CAkS .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-TT62EwK02578CAkS .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-TT62EwK02578CAkS .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TT62EwK02578CAkS .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TT62EwK02578CAkS #statediagram-barbEnd{fill:#333333;}#mermaid-svg-TT62EwK02578CAkS .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TT62EwK02578CAkS .cluster-label,#mermaid-svg-TT62EwK02578CAkS .nodeLabel{color:#131300;}#mermaid-svg-TT62EwK02578CAkS .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-TT62EwK02578CAkS .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-TT62EwK02578CAkS .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-TT62EwK02578CAkS .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-TT62EwK02578CAkS .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-TT62EwK02578CAkS .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-TT62EwK02578CAkS .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-TT62EwK02578CAkS .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-TT62EwK02578CAkS .note-edge{stroke-dasharray:5;}#mermaid-svg-TT62EwK02578CAkS .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-TT62EwK02578CAkS .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-TT62EwK02578CAkS .statediagram-note text{fill:black;}#mermaid-svg-TT62EwK02578CAkS .statediagram-note .nodeLabel{color:black;}#mermaid-svg-TT62EwK02578CAkS .statediagram .edgeLabel{color:red;}#mermaid-svg-TT62EwK02578CAkS #dependencyStart,#mermaid-svg-TT62EwK02578CAkS #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-TT62EwK02578CAkS .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TT62EwK02578CAkS :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
初始化或领导者丢失
选举成为领导者
选举成为跟随者
领导者崩溃
跟随者失去领导者连接
LOOKING
LEADING
FOLLOWING
状态说明:
| 寻找中 | LOOKING | 系统处于选举状态,正在寻找领导者 |
| 跟随中 | FOLLOWING | 作为跟随者,接收并执行领导者的消息 |
| 领导中 | LEADING | 作为领导者,处理客户端请求并广播消息 |
| 观察中 | OBSERVING | 作为观察者,不参与选举和投票 |
2.2 事务 ID(Zxid)
Zxid(ZooKeeper Transaction ID)是一个 64 位的长整型,用于标识事务请求的全局顺序:
Zxid 结构(64 位):
┌─────────────────┬─────────────────┐
│ 高 32 位(epoch)│ 低 32 位(count)│
└─────────────────┴─────────────────┘
- epoch:每次领导者选举后递增,标识领导者代次
- count:领导者内的事务计数,每次写入递增
Zxid 的作用:
3. 领导者选举机制
3.1 选举触发条件
以下情况会触发领导者选举:
| 服务器启动 | 集群初始化 | 所有节点进入 LOOKING 状态 |
| 领导者崩溃 | 检测到领导者失联 | 跟随者进入 LOOKING 状态 |
| 领导者失去连接 | 网络分区导致连接断开 | 重新选举 |
| 集群扩容 | 新节点加入 | 不触发选举,新节点作为跟随者 |
3.2 选举流程详解
ZAB 协议采用 Fast Leader Election(快速领导者选举)算法:
All
服务器5
服务器4
服务器3
服务器2
服务器1
All
服务器5
服务器4
服务器3
服务器2
服务器1
#mermaid-svg-JISBywZPYKoxSuD3{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JISBywZPYKoxSuD3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JISBywZPYKoxSuD3 .error-icon{fill:#552222;}#mermaid-svg-JISBywZPYKoxSuD3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JISBywZPYKoxSuD3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JISBywZPYKoxSuD3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JISBywZPYKoxSuD3 .marker.cross{stroke:#333333;}#mermaid-svg-JISBywZPYKoxSuD3 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JISBywZPYKoxSuD3 p{margin:0;}#mermaid-svg-JISBywZPYKoxSuD3 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JISBywZPYKoxSuD3 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-JISBywZPYKoxSuD3 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JISBywZPYKoxSuD3 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-JISBywZPYKoxSuD3 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-JISBywZPYKoxSuD3 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-JISBywZPYKoxSuD3 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-JISBywZPYKoxSuD3 .sequenceNumber{fill:white;}#mermaid-svg-JISBywZPYKoxSuD3 #sequencenumber{fill:#333;}#mermaid-svg-JISBywZPYKoxSuD3 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-JISBywZPYKoxSuD3 .messageText{fill:#333;stroke:none;}#mermaid-svg-JISBywZPYKoxSuD3 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JISBywZPYKoxSuD3 .labelText,#mermaid-svg-JISBywZPYKoxSuD3 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-JISBywZPYKoxSuD3 .loopText,#mermaid-svg-JISBywZPYKoxSuD3 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-JISBywZPYKoxSuD3 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-JISBywZPYKoxSuD3 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-JISBywZPYKoxSuD3 .noteText,#mermaid-svg-JISBywZPYKoxSuD3 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-JISBywZPYKoxSuD3 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JISBywZPYKoxSuD3 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JISBywZPYKoxSuD3 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-JISBywZPYKoxSuD3 .actorPopupMenu{position:absolute;}#mermaid-svg-JISBywZPYKoxSuD3 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-JISBywZPYKoxSuD3 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-JISBywZPYKoxSuD3 .actor-man circle,#mermaid-svg-JISBywZPYKoxSuD3 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-JISBywZPYKoxSuD3 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
第一轮投票,各自投自己
第二轮投票,S3 获得 5 票
投票 (1, 1)
投票 (2, 2)
投票 (3, 3)
投票 (4, 4)
投票 (5, 5)
比较投票,选择 Zxid 最大的
更新投票 (3, 3)
更新投票 (3, 3)
更新投票 (3, 3)
更新投票 (3, 3)
成为领导者
转为 FOLLOWING
转为 FOLLOWING
转为 FOLLOWING
转为 FOLLOWING
3.3 选举算法实现
投票数据结构(Vote):
/**
* ZAB 协议投票数据结构
* 来源:ZooKeeper 3.8.0 源码
* 文件:org.apache.zookeeper.server.quorum.Vote
*/
public class Vote implements Comparable<Vote> {
// 被推举的服务器 ID
protected final long sid; // Server ID
// 被推举服务器的最新事务 ID
protected final long zxid; // Zxid
// 选举周期,每次选举后递增
protected final long electionEpoch; // 选举时代
// 被推举服务器的状态
protected final ServerState state; // LOOKING/FOLLOWING/LEADING
// 构造函数
public Vote(long sid, long zxid, long electionEpoch, ServerState state) {
this.sid = sid;
this.zxid = zxid;
this.electionEpoch = electionEpoch;
this.state = state;
}
/**
* 投票比较规则(决定谁的投票更优)
* 优先级:electionEpoch > zxid > sid
*/
@Override
public int compareTo(Vote other) {
// 1. 优先比较选举周期(新周期的投票更优)
if (this.electionEpoch != other.electionEpoch) {
return Long.compare(this.electionEpoch, other.electionEpoch);
}
// 2. 选举周期相同,比较 Zxid(数据越新越优)
if (this.zxid != other.zxid) {
return Long.compare(this.zxid, other.zxid);
}
// 3. Zxid 相同,比较服务器 ID(ID 大的更优,保证确定性)
return Long.compare(this.sid, other.sid);
}
}
选举流程关键代码:
/**
* 快速领导者选举算法核心逻辑
* 来源:ZooKeeper 3.8.0 源码
* 文件:org.apache.zookeeper.server.quorum.FastLeaderElection
*/
public class FastLeaderElection implements Election {
/**
* 处理接收到的投票
* 核心逻辑:比较收到的投票与当前投票,保留更优的
*/
protected void processNotification(Notification notification) {
// 1. 解析接收到的投票
Vote vote = notification.vote;
long sid = notification.sid;
// 2. 判断是否切换投票
switch (getPeerState()) {
case LOOKING:
// 当前处于选举状态
// 3. 比较收到的投票与当前持有投票
if (vote.compareTo(currentVote) > 0) {
// 收到的投票更优,更新当前投票
setCurrentVote(vote);
// 4. 广播更新的投票给所有其他服务器
sendNotifications();
// 5. 检查是否已经选举出领导者
if (checkLeaderOutOfElection()) {
// 确定领导者状态(领导者或跟随者)
setPeerState(
currentVote.sid == self.getId() ?
ServerState.LEADING :
ServerState.FOLLOWING
);
// 6. 退出选举
stopElection();
}
}
break;
case FOLLOWING:
case LEADING:
// 已经确定领导者角色,忽略通知
break;
}
}
/**
* 检查是否选举出领导者
* 条件:某个服务器获得超过半数投票
*/
private boolean checkLeaderOutOfElection() {
// 统计每个候选者的投票数
Map<Long, Integer> voteCount = new HashMap<>();
for (Vote vote : recvset.values()) {
voteCount.put(vote.sid,
voteCount.getOrDefault(vote.sid, 0) + 1);
}
// 检查是否有候选者获得过半票数
for (Map.Entry<Long, Integer> entry : voteCount.entrySet()) {
if (entry.getValue() > (getVotingView().size() / 2)) {
return true;
}
}
return false;
}
}
3.4 选举对比表
| 初始化 | 所有节点进入 LOOKING 状态 | Vote(sid, zxid, epoch, LOOKING) | 不需要 |
| 第一轮投票 | 各自投自己 | Vote(sid, zxid, epoch, LOOKING) | UDP 广播 |
| 投票交换 | 交换并比较投票 | Notification(notificationType, vote) | UDP 广播 |
| 投票更新 | 根据 Zxid 等更新投票 | Vote(sid, zxid, epoch, LOOKING) | UDP 广播 |
| 确定领导者 | 某节点获过半票数 | Vote(sid, zxid, epoch, LEADING/FOLLOWING) | 无需通信 |
4. 消息广播流程
领导者选举完成后,进入消息广播阶段,处理客户端的写请求。
4.1 广播流程概览
跟随者3
跟随者2
跟随者1
领导者
客户端
跟随者3
跟随者2
跟随者1
领导者
客户端
#mermaid-svg-yMcobErwvo3aNC2v{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yMcobErwvo3aNC2v .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yMcobErwvo3aNC2v .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yMcobErwvo3aNC2v .error-icon{fill:#552222;}#mermaid-svg-yMcobErwvo3aNC2v .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yMcobErwvo3aNC2v .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yMcobErwvo3aNC2v .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yMcobErwvo3aNC2v .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yMcobErwvo3aNC2v .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yMcobErwvo3aNC2v .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yMcobErwvo3aNC2v .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yMcobErwvo3aNC2v .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yMcobErwvo3aNC2v .marker.cross{stroke:#333333;}#mermaid-svg-yMcobErwvo3aNC2v svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yMcobErwvo3aNC2v p{margin:0;}#mermaid-svg-yMcobErwvo3aNC2v .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMcobErwvo3aNC2v text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-yMcobErwvo3aNC2v .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yMcobErwvo3aNC2v .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-yMcobErwvo3aNC2v .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-yMcobErwvo3aNC2v .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-yMcobErwvo3aNC2v #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-yMcobErwvo3aNC2v .sequenceNumber{fill:white;}#mermaid-svg-yMcobErwvo3aNC2v #sequencenumber{fill:#333;}#mermaid-svg-yMcobErwvo3aNC2v #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-yMcobErwvo3aNC2v .messageText{fill:#333;stroke:none;}#mermaid-svg-yMcobErwvo3aNC2v .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMcobErwvo3aNC2v .labelText,#mermaid-svg-yMcobErwvo3aNC2v .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-yMcobErwvo3aNC2v .loopText,#mermaid-svg-yMcobErwvo3aNC2v .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-yMcobErwvo3aNC2v .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-yMcobErwvo3aNC2v .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-yMcobErwvo3aNC2v .noteText,#mermaid-svg-yMcobErwvo3aNC2v .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-yMcobErwvo3aNC2v .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMcobErwvo3aNC2v .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMcobErwvo3aNC2v .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-yMcobErwvo3aNC2v .actorPopupMenu{position:absolute;}#mermaid-svg-yMcobErwvo3aNC2v .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-yMcobErwvo3aNC2v .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-yMcobErwvo3aNC2v .actor-man circle,#mermaid-svg-yMcobErwvo3aNC2v line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-yMcobErwvo3aNC2v :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
发起写请求(创建节点)
生成新事务(Zxid=0x100000001)
提议写入本地事务日志
广播提议(Proposal)
广播提议(Proposal)
广播提议(Proposal)
写入事务日志
写入事务日志
写入事务日志
发送 ACK(确认)
发送 ACK(确认)
发送 ACK(确认)
收到过半 ACK(3/5)
提交事务(Commit)
广播 COMMIT 消息
广播 COMMIT 消息
广播 COMMIT 消息
返回成功响应
应用到内存数据库
应用到内存数据库
应用到内存数据库
4.2 两阶段提交详解
ZAB 协议采用改进的两阶段提交协议:
| 阶段一:提议 | PROPOSAL | 领导者 | 所有跟随者 | 包含事务内容和 Zxid |
| 阶段一:确认 | ACK | 跟随者 | 领导者 | 表示已写入事务日志 |
| 阶段二:提交 | COMMIT | 领导者 | 所有跟随者 | 表示事务正式生效 |
关键优化:
4.3 消息广播源码分析
提议消息结构(TxnHeader):
/**
* 事务头信息
* 来源:ZooKeeper 3.8.0 源码
* 文件:org.apache.zookeeper.server.TxnHeader
*/
public class TxnHeader implements Record {
// 客户端发起请求时的时间戳
private long clientId;
// 事务类型(创建节点、删除节点、设置数据等)
private int type;
// 事务对应的全局 Zxid
private long zxid;
// 请求发生的时间
private long time;
// 构造函数
public TxnHeader(long clientId, long zxid, int time, int type) {
this.clientId = clientId;
this.zxid = zxid;
this.time = time;
this.type = type;
}
}
领导者广播提议逻辑:
/**
* 领导者处理写请求并广播提议
* 来源:ZooKeeper 3.8.0 源码
* 文件:org.apache.zookeeper.server.quorum.Leader
*/
public class Leader extends QuorumPeer {
/**
* 处理写请求的核心方法
*/
public void processRequest(Request request) {
if (request.type != OpCode.sync) {
// 1. 为请求生成新的 Zxid
long zxid =.getZxid();
request.setHdr(new TxnHeader(
request.sessionId,
zxid, // 分配新的 Zxid
System.currentTimeMillis(),
request.type
));
// 2. 序列化事务请求
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
request.getTxn().serialize(boa, "request");
byte[] data = baos.toByteArray();
// 3. 构建提议对象
Proposal p = new Proposal();
p.packet = new QuorumPacket(Leader.PROPOSAL, request.getHdr().getZxid(),
data, null);
p.request = request;
// 4. 将提议加入已提议队列
outstandingProposals.put(zxid, p);
// 5. 广播提议给所有跟随者
sendPacket(p.packet);
// 6. 写入领导者自己的事务日志
log.write(p.packet);
// 7. 等待跟随者 ACK(异步处理)
waitForAcks();
}
}
/**
* 处理跟随者的 ACK 确认
*/
public void processAck(long sid, long zxid) {
// 1. 记录 ACK
synchronized (this) {
// 将 ACK 加入对应提议的确认集合
if (outstandingProposals.containsKey(zxid)) {
Proposal p = outstandingProposals.get(zxid);
p.ackSet.add(sid);
// 2. 检查是否收到过半 ACK
if (p.ackSet.size() > self.getVotingView().size() / 2) {
// 3. 提交事务
commit(zxid);
// 4. 从已提议队列移除
outstandingProposals.remove(zxid);
// 5. 广播 COMMIT 消息给所有跟随者
broadcastCommit(zxid);
}
}
}
}
/**
* 提交事务并应用到状态机
*/
private void commit(long zxid) {
// 1. 将事务应用到内存数据库
ProcessorRequestProcessor.applyRequest(zxid);
// 2. 唤醒等待的客户端请求
wakeupRequestWaiter(zxid);
}
}
跟随者处理提议逻辑:
/**
* 跟随者处理来自领导者的提议
* 来源:ZooKeeper 3.8.0 源码
* 文件:org.apache.zookeeper.server.quorum.Follower
*/
public class Follower extends QuorumPeer {
/**
* 处理领导者发送的 PROPOSAL 消息
*/
public void processPacket(QuorumPacket qp) {
switch (qp.getType()) {
case Leader.PROPOSAL:
// 1. 反序列化事务请求
ByteArrayInputStream bais = new ByteArrayInputStream(qp.getData());
BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
TxnHeader hdr = new TxnHeader();
Record txn = SerializeUtils.deserializeTxn(bia, hdr);
// 2. 写入本地事务日志(磁盘)
log.logRequest(hdr, txn);
// 3. 发送 ACK 给领导者
QuorumPacket ackPacket = new QuorumPacket(Leader.ACK, qp.getZxid(),
null, null);
writePacket(ackPacket, false);
// 4. 等待 COMMIT 消息(不立即应用到内存)
break;
case Leader.COMMIT:
// 5. 收到 COMMIT 消息,应用到内存数据库
long zxid = qp.getZxid();
log.commit(zxid);
ProcessorRequestProcessor.applyRequest(zxid);
break;
}
}
}
4.4 数据恢复机制
当跟随者重启或新加入集群时,需要从领导者同步数据:
#mermaid-svg-d3JGXPp1YfHW6XJ3{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .error-icon{fill:#552222;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .marker.cross{stroke:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 p{margin:0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-d3JGXPp1YfHW6XJ3 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-d3JGXPp1YfHW6XJ3 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d3JGXPp1YfHW6XJ3 .edgeLabel .label text{fill:#333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .label div .edgeLabel{color:#333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .cluster-label,#mermaid-svg-d3JGXPp1YfHW6XJ3 .nodeLabel{color:#131300;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .note-edge{stroke-dasharray:5;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-note text{fill:black;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagram .edgeLabel{color:red;}#mermaid-svg-d3JGXPp1YfHW6XJ3 #dependencyStart,#mermaid-svg-d3JGXPp1YfHW6XJ3 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-d3JGXPp1YfHW6XJ3 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d3JGXPp1YfHW6XJ3 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
本机 Zxid > 领导者 Zxid
本机数据落后过多
回滚到共同祖先点
从领导者同步缺失事务
从领导者下载最新快照
应用快照后的事务
发现数据不一致
截断日志
同步快照
事务同步
数据一致
下载快照
数据同步方式对比:
| 差异同步 | 数据落后较少 | 仅传输缺失事务 | 短 | LearnerHandler.diff() |
| 快照同步 | 数据落后较多或日志损坏 | 传输完整快照 | 长 | LearnerHandler.snap() |
| 截断同步 | 数据不一致(分叉) | 截断 + 传输差异 | 中 | LearnerHandler.truncLog() |
5. ZAB 与 Paxos 的对比
5.1 协议设计目标对比
| 设计目标 | 专为 ZooKeeper 协调服务设计 | 通用的分布式一致性协议 | 优化吞吐量的 Paxos 实现 |
| 一致性保证 | 顺序一致性 | 安全性(一致性) | 线性一致性 |
| 性能优化 | 主从架构,读性能高 | 每次提议都需要投票 | 选出稳定领导者减少投票 |
| 应用场景 | 配置中心、分布式锁 | 分布式数据库、共识系统 | 分布式数据库、存储系统 |
5.2 机制对比表
| 领导者选举 | Fast Leader Election(快速选举) | 无固定领导者 | 类似 ZAB 的选举机制 |
| 消息轮次 | 两轮(提议 + 提交) | 两轮(Prepare + Accept) | 优化为一轮(Accept) |
| 日志复制 | 领导者驱动,跟随者写入日志 | 提议者广播,接受者投票 | 领导者批量提交 |
| 冲突处理 | 领导者保证无冲突(串行处理) | 通过提案编号避免冲突 | 领导者串行化请求 |
5.3 优缺点对比
#mermaid-svg-3OpEUFpUKuSZsLSM{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3OpEUFpUKuSZsLSM .error-icon{fill:#552222;}#mermaid-svg-3OpEUFpUKuSZsLSM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3OpEUFpUKuSZsLSM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3OpEUFpUKuSZsLSM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3OpEUFpUKuSZsLSM .marker.cross{stroke:#333333;}#mermaid-svg-3OpEUFpUKuSZsLSM svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3OpEUFpUKuSZsLSM p{margin:0;}#mermaid-svg-3OpEUFpUKuSZsLSM .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster-label text{fill:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster-label span{color:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster-label span p{background-color:transparent;}#mermaid-svg-3OpEUFpUKuSZsLSM .label text,#mermaid-svg-3OpEUFpUKuSZsLSM span{fill:#333;color:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM .node rect,#mermaid-svg-3OpEUFpUKuSZsLSM .node circle,#mermaid-svg-3OpEUFpUKuSZsLSM .node ellipse,#mermaid-svg-3OpEUFpUKuSZsLSM .node polygon,#mermaid-svg-3OpEUFpUKuSZsLSM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3OpEUFpUKuSZsLSM .rough-node .label text,#mermaid-svg-3OpEUFpUKuSZsLSM .node .label text,#mermaid-svg-3OpEUFpUKuSZsLSM .image-shape .label,#mermaid-svg-3OpEUFpUKuSZsLSM .icon-shape .label{text-anchor:middle;}#mermaid-svg-3OpEUFpUKuSZsLSM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3OpEUFpUKuSZsLSM .rough-node .label,#mermaid-svg-3OpEUFpUKuSZsLSM .node .label,#mermaid-svg-3OpEUFpUKuSZsLSM .image-shape .label,#mermaid-svg-3OpEUFpUKuSZsLSM .icon-shape .label{text-align:center;}#mermaid-svg-3OpEUFpUKuSZsLSM .node.clickable{cursor:pointer;}#mermaid-svg-3OpEUFpUKuSZsLSM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3OpEUFpUKuSZsLSM .arrowheadPath{fill:#333333;}#mermaid-svg-3OpEUFpUKuSZsLSM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3OpEUFpUKuSZsLSM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3OpEUFpUKuSZsLSM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3OpEUFpUKuSZsLSM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3OpEUFpUKuSZsLSM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3OpEUFpUKuSZsLSM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster text{fill:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM .cluster span{color:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3OpEUFpUKuSZsLSM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3OpEUFpUKuSZsLSM rect.text{fill:none;stroke-width:0;}#mermaid-svg-3OpEUFpUKuSZsLSM .icon-shape,#mermaid-svg-3OpEUFpUKuSZsLSM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3OpEUFpUKuSZsLSM .icon-shape p,#mermaid-svg-3OpEUFpUKuSZsLSM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3OpEUFpUKuSZsLSM .icon-shape .label rect,#mermaid-svg-3OpEUFpUKuSZsLSM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3OpEUFpUKuSZsLSM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3OpEUFpUKuSZsLSM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3OpEUFpUKuSZsLSM :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Paxos
优点
理论完备性强
通用性强
支持多提议者
缺点
实现复杂度高
需要多轮通信
吞吐量受限
ZAB
优点
专为协调服务优化
顺序一致性保证
读性能高(主从架构)
缺点
依赖领导者(单点故障)
通用性不如 Paxos
6. 实战:ZooKeeper 源码分析
6.1 环境准备
ZooKeeper 版本: 3.8.0(最新稳定版)
源码获取:
# 克隆 ZooKeeper 源码仓库
git clone https://github.com/apache/zookeeper.git
cd zookeeper
git checkout release-3.8.0
# 构建项目(需要 Maven 和 JDK 8+)
mvn clean install -DskipTests
核心模块结构:
zookeeper-server/src/main/java/org/apache/zookeeper/server/quorum/
├── QuorumPeer.java # 服务器节点基类
├── Leader.java # 领导者实现
├── Follower.java # 跟随者实现
├── FastLeaderElection.java # 快速领导者选举
├── Vote.java # 投票数据结构
├── Proposal.java # 提议数据结构
└── LearnerHandler.java # 学习者处理器
6.2 关键类关系图
#mermaid-svg-3qHok6Cvbp7OyjKi{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3qHok6Cvbp7OyjKi .error-icon{fill:#552222;}#mermaid-svg-3qHok6Cvbp7OyjKi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3qHok6Cvbp7OyjKi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3qHok6Cvbp7OyjKi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3qHok6Cvbp7OyjKi .marker.cross{stroke:#333333;}#mermaid-svg-3qHok6Cvbp7OyjKi svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3qHok6Cvbp7OyjKi p{margin:0;}#mermaid-svg-3qHok6Cvbp7OyjKi g.classGroup text{fill:#9370DB;stroke:none;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-3qHok6Cvbp7OyjKi g.classGroup text .title{font-weight:bolder;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster-label text{fill:#333;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster-label span{color:#333;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster-label span p{background-color:transparent;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster text{fill:#333;}#mermaid-svg-3qHok6Cvbp7OyjKi .cluster span{color:#333;}#mermaid-svg-3qHok6Cvbp7OyjKi .nodeLabel,#mermaid-svg-3qHok6Cvbp7OyjKi .edgeLabel{color:#131300;}#mermaid-svg-3qHok6Cvbp7OyjKi .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-3qHok6Cvbp7OyjKi .label text{fill:#131300;}#mermaid-svg-3qHok6Cvbp7OyjKi .labelBkg{background:#ECECFF;}#mermaid-svg-3qHok6Cvbp7OyjKi .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-3qHok6Cvbp7OyjKi .classTitle{font-weight:bolder;}#mermaid-svg-3qHok6Cvbp7OyjKi .node rect,#mermaid-svg-3qHok6Cvbp7OyjKi .node circle,#mermaid-svg-3qHok6Cvbp7OyjKi .node ellipse,#mermaid-svg-3qHok6Cvbp7OyjKi .node polygon,#mermaid-svg-3qHok6Cvbp7OyjKi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3qHok6Cvbp7OyjKi .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi g.clickable{cursor:pointer;}#mermaid-svg-3qHok6Cvbp7OyjKi g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-3qHok6Cvbp7OyjKi g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-3qHok6Cvbp7OyjKi .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-3qHok6Cvbp7OyjKi .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-3qHok6Cvbp7OyjKi .dashed-line{stroke-dasharray:3;}#mermaid-svg-3qHok6Cvbp7OyjKi .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-3qHok6Cvbp7OyjKi #compositionStart,#mermaid-svg-3qHok6Cvbp7OyjKi .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #compositionEnd,#mermaid-svg-3qHok6Cvbp7OyjKi .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #dependencyStart,#mermaid-svg-3qHok6Cvbp7OyjKi .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #dependencyStart,#mermaid-svg-3qHok6Cvbp7OyjKi .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #extensionStart,#mermaid-svg-3qHok6Cvbp7OyjKi .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #extensionEnd,#mermaid-svg-3qHok6Cvbp7OyjKi .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #aggregationStart,#mermaid-svg-3qHok6Cvbp7OyjKi .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #aggregationEnd,#mermaid-svg-3qHok6Cvbp7OyjKi .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #lollipopStart,#mermaid-svg-3qHok6Cvbp7OyjKi .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi #lollipopEnd,#mermaid-svg-3qHok6Cvbp7OyjKi .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-3qHok6Cvbp7OyjKi .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-3qHok6Cvbp7OyjKi .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3qHok6Cvbp7OyjKi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3qHok6Cvbp7OyjKi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3qHok6Cvbp7OyjKi :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
QuorumPeer
-ServerState state
-long myid
-QuorumCnxManager cxnManager
+start()
+run()
Leader
-LeaderZooKeeperServer zk
-LinkedBlockingQueue<Request> proposedRequests
+lead()
+processRequest(Request)
Follower
-FollowerZooKeeperServer zk
+followLeader()
+processPacket(QuorumPacket)
FastLeaderElection
-LinkedBlockingQueue<Notification> recvqueue
-Vote currentVote
+lookForLeader()
+processNotification(Notification)
Vote
-long sid
-long zxid
-long electionEpoch
+compareTo(Vote)
6.3 完整可运行示例
以下是一个简化版的 ZAB 协议模拟实现:
/**
* 简化版 ZAB 协议模拟实现
* 用于理解核心原理,非生产代码
*/
public class ZabProtocolSimulator {
/**
* 服务器状态枚举
*/
enum ServerState {
LOOKING, // 寻找领导者
FOLLOWING, // 跟随者
LEADING // 领导者
}
/**
* 投票类(对应源码 Vote)
*/
static class Vote {
long sid; // 服务器 ID
long zxid; // 最新事务 ID
long epoch; // 选举周期
Vote(long sid, long zxid, long epoch) {
this.sid = sid;
this.zxid = zxid;
this.epoch = epoch;
}
/**
* 投票比较:优先级 epoch > zxid > sid
*/
int compareTo(Vote other) {
if (this.epoch != other.epoch) {
return Long.compare(this.epoch, other.epoch);
}
if (this.zxid != other.zxid) {
return Long.compare(this.zxid, other.zxid);
}
return Long.compare(this.sid, other.sid);
}
@Override
public String toString() {
return String.format("Vote(sid=%d, zxid=0x%x, epoch=%d)",
sid, zxid, epoch);
}
}
/**
* 服务器节点模拟类
*/
static class ServerNode {
long sid; // 服务器 ID
ServerState state; // 当前状态
Vote currentVote; // 当前投票
long currentEpoch; // 当前选举周期
long lastZxid; // 最新事务 ID
// 模拟网络通信
Map<Long, ServerNode> cluster;
ServerNode(long sid, Map<Long, ServerNode> cluster) {
this.sid = sid;
this.cluster = cluster;
this.state = ServerState.LOOKING;
this.currentEpoch = 0;
this.lastZxid = 0;
// 初始投自己一票
this.currentVote = new Vote(sid, lastZxid, currentEpoch);
}
/**
* 模拟领导者选举
*/
void lookForLeader() {
System.out.printf("[节点%d] 进入选举状态,初始投票: %s%n",
sid, currentVote);
state = ServerState.LOOKING;
currentEpoch++;
// 第一轮:各自投自己
broadcastVote(currentVote);
// 模拟多轮投票交换
for (int round = 0; round < 3; round++) {
try {
Thread.sleep(100); // 模拟网络延迟
// 收集所有投票
Map<Vote, Integer> voteCount = new HashMap<>();
for (ServerNode node : cluster.values()) {
Vote vote = node.currentVote;
voteCount.put(vote, voteCount.getOrDefault(vote, 0) + 1);
// 比较并更新自己的投票
if (vote.compareTo(currentVote) > 0) {
System.out.printf("[节点%d] 收到更优投票: %s%n",
sid, vote);
currentVote = vote;
broadcastVote(currentVote);
}
}
// 检查是否有节点获得过半票数
int quorum = cluster.size() / 2 + 1;
for (Map.Entry<Vote, Integer> entry : voteCount.entrySet()) {
if (entry.getValue() >= quorum) {
Vote winner = entry.getKey();
System.out.printf("[节点%d] 选举完成!领导者: 节点%d " +
"(得票数: %d/%d)%n",
sid, winner.sid, entry.getValue(),
cluster.size());
if (winner.sid == sid) {
state = ServerState.LEADING;
System.out.printf("[节点%d] 成为领导者!%n", sid);
} else {
state = ServerState.FOLLOWING;
System.out.printf("[节点%d] 成为跟随者," +
"跟随领导者: 节点%d%n",
sid, winner.sid);
}
return;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
/**
* 广播投票给其他节点
*/
void broadcastVote(Vote vote) {
for (ServerNode node : cluster.values()) {
if (node.sid != this.sid) {
node.receiveVote(this.sid, vote);
}
}
}
/**
* 接收其他节点的投票
*/
void receiveVote(long fromSid, Vote vote) {
// 在真实实现中,会将投票放入接收队列异步处理
// 这里简化为直接比较
if (vote.compareTo(currentVote) > 0) {
currentVote = vote;
}
}
}
/**
* 主函数:模拟 5 节点集群选举
*/
public static void main(String[] args) throws InterruptedException {
System.out.println("=== ZAB 协议领导者选举模拟 ===\\n");
// 创建 5 节点集群
Map<Long, ServerNode> cluster = new HashMap<>();
for (long sid = 1; sid <= 5; sid++) {
cluster.put(sid, new ServerNode(sid, cluster));
}
// 模拟节点5的Zxid更大(数据更新)
cluster.get(5).lastZxid = 0x100;
cluster.get(5).currentVote = new Vote(5, 0x100, 0);
// 所有节点开始选举
System.out.println("— 开始选举 —\\n");
List<Thread> threads = new ArrayList<>();
for (ServerNode node : cluster.values()) {
Thread t = new Thread(() -> node.lookForLeader());
t.start();
threads.add(t);
}
// 等待所有节点完成选举
for (Thread t : threads) {
t.join();
}
// 输出最终结果
System.out.println("\\n— 选举结果 —");
for (ServerNode node : cluster.values()) {
System.out.printf("节点%d: %s%n", node.sid, node.state);
}
}
}
运行结果示例:
=== ZAB 协议领导者选举模拟 ===
— 开始选举 —
[节点1] 进入选举状态,初始投票: Vote(sid=1, zxid=0x0, epoch=1)
[节点2] 进入选举状态,初始投票: Vote(sid=2, zxid=0x0, epoch=1)
[节点3] 进入选举状态,初始投票: Vote(sid=3, zxid=0x0, epoch=1)
[节点4] 进入选举状态,初始投票: Vote(sid=4, zxid=0x0, epoch=1)
[节点5] 进入选举状态,初始投票: Vote(sid=5, zxid=0x100, epoch=1)
[节点1] 收到更优投票: Vote(sid=5, zxid=0x100, epoch=1)
[节点2] 收到更优投票: Vote(sid=5, zxid=0x100, epoch=1)
[节点3] 收到更优投票: Vote(sid=5, zxid=0x100, epoch=1)
[节点4] 收到更优投票: Vote(sid=5, zxid=0x100, epoch=1)
[节点1] 选举完成!领导者: 节点5 (得票数: 5/5)
[节点1] 成为跟随者,跟随领导者: 节点5
[节点2] 选举完成!领导者: 节点5 (得票数: 5/5)
[节点2] 成为跟随者,跟随领导者: 节点5
[节点3] 选举完成!领导者: 节点5 (得票数: 5/5)
[节点3] 成为跟随者,跟随领导者: 节点5
[节点4] 选举完成!领导者: 节点5 (得票数: 5/5)
[节点4] 成为跟随者,跟随领导者: 节点5
[节点5] 选举完成!领导者: 节点5 (得票数: 5/5)
[节点5] 成为领导者!
— 选举结果 —
节点1: FOLLOWING
节点2: FOLLOWING
节点3: FOLLOWING
节点4: FOLLOWING
节点5: LEADING
7. 总结与最佳实践
7.1 核心要点回顾
| ZAB 协议作用 | 保证 ZooKeeper 集群的数据一致性和高可用性 |
| 服务器状态 | LOOKING、FOLLOWING、LEADING、OBSERVING |
| Zxid 结构 | 高32位 epoch(选举周期)+ 低32位 count(事务计数) |
| 选举算法 | Fast Leader Election,基于 Zxid 和 sid 投票 |
| 消息广播 | 两阶段提交(PROPOSAL + ACK + COMMIT) |
| 过半原则 | 所有决策(选举、提交)都需要超过半数节点同意 |
7.2 ZAB 协议流程图总结
#mermaid-svg-cKIu6eaN3gtgIDPI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cKIu6eaN3gtgIDPI .error-icon{fill:#552222;}#mermaid-svg-cKIu6eaN3gtgIDPI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cKIu6eaN3gtgIDPI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cKIu6eaN3gtgIDPI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cKIu6eaN3gtgIDPI .marker.cross{stroke:#333333;}#mermaid-svg-cKIu6eaN3gtgIDPI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cKIu6eaN3gtgIDPI p{margin:0;}#mermaid-svg-cKIu6eaN3gtgIDPI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster-label text{fill:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster-label span{color:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster-label span p{background-color:transparent;}#mermaid-svg-cKIu6eaN3gtgIDPI .label text,#mermaid-svg-cKIu6eaN3gtgIDPI span{fill:#333;color:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI .node rect,#mermaid-svg-cKIu6eaN3gtgIDPI .node circle,#mermaid-svg-cKIu6eaN3gtgIDPI .node ellipse,#mermaid-svg-cKIu6eaN3gtgIDPI .node polygon,#mermaid-svg-cKIu6eaN3gtgIDPI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cKIu6eaN3gtgIDPI .rough-node .label text,#mermaid-svg-cKIu6eaN3gtgIDPI .node .label text,#mermaid-svg-cKIu6eaN3gtgIDPI .image-shape .label,#mermaid-svg-cKIu6eaN3gtgIDPI .icon-shape .label{text-anchor:middle;}#mermaid-svg-cKIu6eaN3gtgIDPI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cKIu6eaN3gtgIDPI .rough-node .label,#mermaid-svg-cKIu6eaN3gtgIDPI .node .label,#mermaid-svg-cKIu6eaN3gtgIDPI .image-shape .label,#mermaid-svg-cKIu6eaN3gtgIDPI .icon-shape .label{text-align:center;}#mermaid-svg-cKIu6eaN3gtgIDPI .node.clickable{cursor:pointer;}#mermaid-svg-cKIu6eaN3gtgIDPI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cKIu6eaN3gtgIDPI .arrowheadPath{fill:#333333;}#mermaid-svg-cKIu6eaN3gtgIDPI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cKIu6eaN3gtgIDPI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cKIu6eaN3gtgIDPI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cKIu6eaN3gtgIDPI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cKIu6eaN3gtgIDPI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cKIu6eaN3gtgIDPI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster text{fill:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI .cluster span{color:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cKIu6eaN3gtgIDPI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cKIu6eaN3gtgIDPI rect.text{fill:none;stroke-width:0;}#mermaid-svg-cKIu6eaN3gtgIDPI .icon-shape,#mermaid-svg-cKIu6eaN3gtgIDPI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cKIu6eaN3gtgIDPI .icon-shape p,#mermaid-svg-cKIu6eaN3gtgIDPI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cKIu6eaN3gtgIDPI .icon-shape .label rect,#mermaid-svg-cKIu6eaN3gtgIDPI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cKIu6eaN3gtgIDPI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cKIu6eaN3gtgIDPI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cKIu6eaN3gtgIDPI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
否
是
是
否
系统启动
所有节点进入 LOOKING
开始领导者选举
第一轮投票:各自投自己
交换投票并比较
收到过半投票?
确定领导者/跟随者
进入消息广播阶段
客户端发送写请求
领导者生成 PROPOSAL
所有节点写入事务日志
跟随者发送 ACK
收到过半ACK?
领导者发送 COMMIT
所有节点应用到内存
检测到领导者故障?
7.3 生产环境最佳实践
1. 集群规模建议:
| 3 节点 | 1 | 中 | 中 | 测试环境、小规模生产 |
| 5 节点 | 2 | 高 | 高 | 推荐:生产环境标准配置 |
| 7 节点 | 3 | 很高 | 中低 | 大规模集群、跨机房部署 |
2. 配置优化要点:
# zookeeper.conf 关键配置
# 心跳超时时间(毫秒)
tickTime=2000
# 初始同步超时(10个心跳 = 20秒)
initLimit=10
# 数据同步超时(5个心跳 = 10秒)
syncLimit=5
# 数据目录
dataDir=/var/lib/zookeeper
# 客户端连接端口
clientPort=2181
# 集群节点配置(server.1=localhost:2888:3888)
# 2888: 端口用于跟随者连接领导者
# 3888: 端口用于领导者选举
3. 监控指标:
| 选举指标 | 选举次数 | 正常情况应为0(稳定期) | 频繁选举说明网络不稳定 |
| 选举指标 | 选举耗时 | < 30秒 | 选举超长可能是网络问题 |
| 性能指标 | 请求延迟 | < 10ms(局域网) | p99延迟应监控 |
| 性能指标 | 吞吐量 | 根据业务配置 | 写入受限于领导者处理能力 |
| 健康指标 | 节点状态 | 应为 LEADING/FOLLOWING | LOOKING状态说明集群异常 |
| 健康指标 | 数据包丢失率 | < 0.01% | 丢包导致频繁重传 |
| 健康指标 | 磁盘使用率 | < 80% | 事务日志应保持充足空间 |
4. 故障排查指南:
#mermaid-svg-Er0Gq35Q9Q6GM46U{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Er0Gq35Q9Q6GM46U .error-icon{fill:#552222;}#mermaid-svg-Er0Gq35Q9Q6GM46U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Er0Gq35Q9Q6GM46U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .marker.cross{stroke:#333333;}#mermaid-svg-Er0Gq35Q9Q6GM46U svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Er0Gq35Q9Q6GM46U p{margin:0;}#mermaid-svg-Er0Gq35Q9Q6GM46U .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster-label text{fill:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster-label span{color:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster-label span p{background-color:transparent;}#mermaid-svg-Er0Gq35Q9Q6GM46U .label text,#mermaid-svg-Er0Gq35Q9Q6GM46U span{fill:#333;color:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .node rect,#mermaid-svg-Er0Gq35Q9Q6GM46U .node circle,#mermaid-svg-Er0Gq35Q9Q6GM46U .node ellipse,#mermaid-svg-Er0Gq35Q9Q6GM46U .node polygon,#mermaid-svg-Er0Gq35Q9Q6GM46U .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .rough-node .label text,#mermaid-svg-Er0Gq35Q9Q6GM46U .node .label text,#mermaid-svg-Er0Gq35Q9Q6GM46U .image-shape .label,#mermaid-svg-Er0Gq35Q9Q6GM46U .icon-shape .label{text-anchor:middle;}#mermaid-svg-Er0Gq35Q9Q6GM46U .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .rough-node .label,#mermaid-svg-Er0Gq35Q9Q6GM46U .node .label,#mermaid-svg-Er0Gq35Q9Q6GM46U .image-shape .label,#mermaid-svg-Er0Gq35Q9Q6GM46U .icon-shape .label{text-align:center;}#mermaid-svg-Er0Gq35Q9Q6GM46U .node.clickable{cursor:pointer;}#mermaid-svg-Er0Gq35Q9Q6GM46U .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .arrowheadPath{fill:#333333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Er0Gq35Q9Q6GM46U .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Er0Gq35Q9Q6GM46U .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Er0Gq35Q9Q6GM46U .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster text{fill:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U .cluster span{color:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Er0Gq35Q9Q6GM46U .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Er0Gq35Q9Q6GM46U rect.text{fill:none;stroke-width:0;}#mermaid-svg-Er0Gq35Q9Q6GM46U .icon-shape,#mermaid-svg-Er0Gq35Q9Q6GM46U .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Er0Gq35Q9Q6GM46U .icon-shape p,#mermaid-svg-Er0Gq35Q9Q6GM46U .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Er0Gq35Q9Q6GM46U .icon-shape .label rect,#mermaid-svg-Er0Gq35Q9Q6GM46U .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Er0Gq35Q9Q6GM46U .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Er0Gq35Q9Q6GM46U .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Er0Gq35Q9Q6GM46U :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
选举失败
数据不一致
性能下降
检测到故障
故障类型
ElectFault
检查网络连通性
检查配置文件myid
检查时钟同步
DataFault
检查事务日志完整性
手动触发快照同步
重建节点数据目录
PerfFault
检查磁盘IO瓶颈
检查内存使用
检查JVM GC日志
修复网络配置
修正myid配置
配置NTP同步
7.4 ZAB 协议的局限性
| 领导者瓶颈 | 所有写请求由领导者处理 | 写性能受限 | 读操作分离到跟随者 |
| 网络分区敏感 | 脑裂可能导致双领导者 | 数据不一致 | 过半机制+观察者模式 |
| 选举期间不可用 | 选举期间无法处理写请求 | 可用性下降 | 快速选举优化 |
| 日志膨胀 | 事务日志持续增长 | 磁盘压力 | 定期快照+日志清理 |
| 跨地域延迟 | 广播协议对网络延迟敏感 | 性能下降 | 同地域部署+异步观察者 |
7.5 学习路径建议
对于想深入理解 ZAB 协议的开发者,建议按以下路径学习:
第一阶段:理论基础(1-2周)
├── 学习分布式系统基础理论
│ ├── CAP定理
│ ├── 分布式一致性模型
│ └── 共识算法(Paxos/Raft)
└── 阅读 ZooKeeper 官方文档
第二阶段:源码阅读(2-4周)
├── 核心模块源码
│ ├── FastLeaderElection.java(选举算法)
│ ├── Leader.java(领导者逻辑)
│ └── Follower.java(跟随者逻辑)
├── 调试 ZooKeeper 源码
└── 运行本文提供的示例代码
第三阶段:实践应用(持续)
├── 部署生产级 ZooKeeper 集群
├── 性能测试与调优
├── 故障演练
└── 源码级问题排查
结语
ZAB 协议是 ZooKeeper 的核心,它巧妙地将领导者选举和原子广播结合在一起,为分布式协调服务提供了可靠的一致性保证。通过本文的学习,我们深入理解了:
掌握 ZAB 协议不仅能帮助我们更好地使用 ZooKeeper,也为理解其他分布式系统(如 Kafka 的 ISR 机制、Etcd 的 Raft 协议)奠定了基础。
继续学习:
- 对比学习 Raft 协议(Etcd、Consul)
- 研究 ZooKeeper 的 Watch 机制实现
- 探索 ZooKeeper 在实际项目中的应用场景(服务发现、分布式锁、配置中心)
参考资源:
关于作者:
本文由 AI 技术博客作者撰写,专注于分布式系统、大数据技术栈的深度解析。如有疑问或建议,欢迎在评论区讨论。
版权声明:
本文为原创技术文章,转载请注明出处。源码部分来自 Apache ZooKeeper 3.8.0,遵循 Apache License 2.0 开源协议。







