4.2 快速选举:崩溃恢复后的重新开庭


文档摘要

4.2 快速选举:崩溃恢复后的重新开庭 本节摘要:Leader 失联后,剩余节点启动 FastLeaderElection:各自以(纪元,zxid,myid)为凭拉票,投票经多轮交换向最优者收敛,过半认可即当选。本节拆解选票结构与比较规则、收敛过程与关键时序参数,并解释选举期间为何拒绝对外服务。 审理一桩"换届案" 2.2 节你亲手停掉 Leader、目睹过几秒内的新主上任。现在把那几秒逐帧放慢。设三台机器 S1、S2、S3,S1 是在任 Leader,此刻掉电。S2 与 S3 几乎同时发现与 S1 的心跳超时——它们各自进入 LOOKING 状态,向全集群(包括自己)广播第一张选票。选举的全部戏剧性,都在接下来几百毫秒的选票交换里。

4.2 快速选举:崩溃恢复后的重新开庭

本节摘要:Leader 失联后,剩余节点启动 FastLeaderElection:各自以(纪元,zxid,myid)为凭拉票,投票经多轮交换向最优者收敛,过半认可即当选。本节拆解选票结构与比较规则、收敛过程与关键时序参数,并解释选举期间为何拒绝对外服务。

审理一桩"换届案"

2.2 节你亲手停掉 Leader、目睹过几秒内的新主上任。现在把那几秒逐帧放慢。设三台机器 S1、S2、S3,S1 是在任 Leader,此刻掉电。S2 与 S3 几乎同时发现与 S1 的心跳超时——它们各自进入 LOOKING 状态,向全集群(包括自己)广播第一张选票。选举的全部戏剧性,都在接下来几百毫秒的选票交换里。

选票怎么构造,怎么比较

每张选票是一个三元组:投票目标 + 目标的纪元 epoch + 目标的 zxid,附上投票者自己的 myid。比较规则写死在协议里,优先级从高到低三条:先比已提交数据的最大纪元,大者新;再比事务号 zxid,大者新;都相同才比 myid,大者优先。一句话概括:谁手里的卷宗更新谁当选,卷宗一样新才论资排辈

这条规则的设计意图清晰得近乎直白:新 Leader 必须掌握全部已提交数据,否则提交过的判决会凭空消失。myid 只是同数据时的决胜票,不是能力评分——运维里"把某台配成大 myid 好让它当主"是无效操作,除非各机数据完全同步。

图:一轮选举中选票的交换与收敛

图:一轮选举中选票的交换与收敛

收敛为何不会进入死循环?因为比较规则是全序的:任何两张选票必能分出高下,投票者看到更优的票就改投并广播,选票目标单调向"全集群最优"靠拢。过半认可即定案,剩余节点无论多慢,最终都会被 majority 的选票同化。

从选票到上任:还差三步

过半选票不是终点。当选者先提高纪元号并持久化(宣告"本届法庭成立"),随后进入 4.1 节的恢复流程:与各 Follower 交换事务日志,把已提交提案补齐、把未提交提案作废,对齐完成后才开始受理新提案。这一段对外的表现是:选举期间,集群不响应任何读写——客户端会收到超时或连接拒绝,SDK 据此换节点重试。

# 复现一次换届:三节点集群,停掉 Leader 后盯日志 $ bin/zkServer.sh stop # 在 Leader 上执行 # 立刻 tail 另一台的日志,会看到完整换届记录(节选): # INFO [QuorumPeer[myid=2]/...:FastLeaderElection@866] - Adding vote # WARN [QuorumPeer[myid=2]/...:QuorumCnxManager@584] - Cannot open channel to 1 # INFO [QuorumPeer[myid=2]/...:QuorumPeer@1188] - FOLLOWING # --> 换届结束,本机进入 FOLLOWING;若本机当选则输出 LEADING # 全程秒级。四字命令确认新角色 $ echo stat | nc node2 2181 | findstr Mode Mode: leader

日志里那行 Cannot open channel to 1 值得一眼认出:它就是 2.2 节防火墙未放行 3888 端口时反复刷屏的同一句话。区别只在语境——掉电场景里它昭告换届开始,配置错误场景里它宣告换届永远完不成。

时序参数:选举不是零成本的

换届期间的不可用窗口由三段构成:故障感知(tickTime 级心跳超时,syncLimit 相关)、选票收敛(网络往返与数据比对,通常亚秒)、日志对齐(与数据差异量成正比,海量 ZNode 或长尾日志时明显变长)。2.3 节的 initLimit 在这里兑现它的意义——它约束的是"新 Follower 追平 Leader 允许多少个 tick",对齐不出来的节点会被挡在庭外。

# 观察对齐成本:造一批数据后重演换届,对比恢复时长 $ for i in 1 2 3 4 5 6 7 8 9 10; do zkCli.sh -server node2:2181 \ create /load/key$i "value$i"; done # 逐个 create 各自是一次提案,十次产生十条日志——数据差异越大, # 对齐阶段越长;这也是 2.3 节"写密集集群把 initLimit 放宽"的原因

有个值得单独记住的边界场景:整个集群冷启动时所有节点都是 LOOKING,第一轮交换即完成选举,但因为尚无已提交数据,比较主要落在 zxid 与 myid 上。所以你会观察到"重启集群后主节点可能换人"——这不是故障,是规则的自然结果。

要点回顾

  • 选票三元组:目标、纪元、zxid,比较规则先看数据新旧、再看届内序号、最后才看 myid。
  • 收敛靠全序:选票单调流向更优者,过半即定案;myid 不是能力值,别试图"指定主节点"。
  • 不可用窗口三段:感知、收敛、对齐;对齐成本与数据差异正相关,参数放宽治标不治本。
  • 复活者守则:掉电的老 Leader 醒来发现纪元落后,自动降级为 Follower,不会出现双主。

常见疑问

问:选举期间客户端报错正常吗? 正常且必要。换届的秒级停摆是 CP 语义的一部分,客户端 SDK 的换节点重试会消化掉绝大多数场景;若业务连秒级不可用都敏感,说明协调数据需要的是 AP 型系统——7.3 节会给出替代清单。

选举案审结。下一个案件回到旁听席:节点变了,当事人凭什么第一时间知道。

选票比较的伪代码

把比较规则写成十几行伪代码,比文字描述更不容易忘。它也是读源码时定位比较逻辑的地图:

// 选票比较:正数表示 left 更优(更值得投) int compare(Vote left, Vote right) { if (left.epoch != right.epoch) return left.epoch - right.epoch; // 第一优先:纪元,届数新者胜 if (left.zxid != right.zxid) return left.zxid - right.zxid; // 第二优先:事务号,日志新者胜 return left.myid - right.myid; // 第三优先:机器编号,仅作决胜 } // 投票者循环:收到的票若比自己当前的票更优,则改投并重新广播

三个优先级的次序不可调换。若把 myid 放在 zxid 前面,数据旧的节点可能当选,已提交的提案将丢失——协议正确性的全部重量都压在这三行的次序上。

换届监控:把秒级窗口纳入巡检

换届本身正常,频繁换届不正常(网络抖动、GC 停顿、磁盘 hung 的综合信号)。6.4 节的巡检脚本里应加一条:解析各节点日志里的 LOOKING 出现频率,单机一小时内超过一次即触发排查。这条告警在真实环境里多次先于业务报障发现硬件劣化——法庭自己"换庭"的频率,就是集群健康的脉搏。

延伸追问

问:选举有没有可能永远选不出来? 配置正确时不会——全序比较保证收敛。选不出来的现实原因只有两类:多数派机器同时失联(此时罢工是正确行为),或 3888 端口互通被防火墙截断(2.2 节的部署坑)。排障时先查连通性再看协议。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U