先记住这个答案
每个任期都有唯一编号,候选人发起选举时任期加一,节点对每个任期只投一票,只有获得多数派投票的候选人才能成为 Leader。多数派之间必有交集,因此同一任期不可能有两个 Leader 都获得多数票。
- 任期是单调递增的逻辑时钟。
- 每个节点每任期只能投一票。
- 多数派必有交集,所以不会双主。
任期与投票如何锁死唯一性
Raft 将时间划分为任意长度的任期(term),每个任期有唯一整数编号。节点启动时从持久化状态读取当前任期,候选人发起选举时先自增任期号,节点收到请求后会更新自己的任期。任期作为逻辑时钟,让过期消息能被识别并拒绝,避免旧 Leader 或旧候选人干扰当前选举。
每个节点对同一任期只能投一票,记录在持久化的 votedFor 字段中。候选人获得严格多数(N/2+1)的投票即成为 Leader。多数派之间必然有交集,因为两个多数派至少共享一个节点,而该节点不可能同时投票给两个候选人,所以同一任期不可能产生两个 Leader。
五节点集群的分区选举场景
假设一个五节点集群,节点 A、B、C 在一侧,D、E 在另一侧,分区发生。A 的选举超时触发,任期从 1 变为 2,向可达的 B、C 请求投票。B、C 还没有收到更高任期,各自投给 A,这样 A 获得三票(自己、B、C),达到多数,成为分区一侧的 Leader。
D、E 无法联系 A 组,它们会不断重试选举,任期递增到 3、4……但由于只有两个节点,永远无法达到多数。集群在少数派一侧无法选出 Leader,所以不会出现脑裂。即使 D 在任期内得到 E 的选票,也仅有 2 票,不满足 3/5 的多数。
选举安全性依赖的边界条件
如果节点时钟或网络导致任期跳跃,比如一个节点因消息延迟收到更高任期,它会更新自己的任期并拒绝旧的候选人请求。但任期号本身不受时钟影响,只靠递增计数器,所以不存在时钟漂移问题。真正危险的是节点持久化存储损坏,导致 votedFor 丢失,可能在同一任期投出两票,但 Raft 要求持久化存储必须可靠。
另一个边界是“旧日志”候选人:如果某个候选人日志落后,但它仍可能当选,导致新 Leader 没有全部已提交日志。Raft 通过选举限制(候选人必须包含所有已提交日志)来解决,而不是用投票机制本身。因此,投票机制只能保证唯一 Leader,不保证日志完整,必须配合选举限制。
容易答错的地方
- 认为任期相同也会有两个 Leader
- 有说法认为网络分区会造成双主,但 Raft 要求多数派投票,分区后少数派永远无法获得多数选票,所以不可能产生第二个 Leader。双主场景通常来自未正确实现多数派或允许旧 Leader 存留。
- 把 PreVote 当成选举安全的一部分
- PreVote 是为了解决网络分区恢复时节点频繁打断选举的问题,它不改变投票规则,只增加一个预投票阶段。安全性仍然依赖任期和多数派,PreVote 只是优化可用性。
面试官还会怎么问?
投票时如何防止节点对同一候选人重复投票?
每个节点持久化 votedFor 记录当前任期已经投给了谁,如果收到同一任期的重复投票请求,且 votedFor 不是当前候选人,则拒绝;如果候选人相同,直接授予已经承诺的票。
如果两个候选人同时获得相同票数怎么办?
Raft 通过随机选举超时来减少平局概率。如果票数相同(例如各得一半),没有多数派,选举失败,所有节点超时后重新发起选举,任期递增,继续竞争。
网络分区恢复后,旧 Leader 如何处理?
旧 Leader(例如 A)收到更高任期的消息后,会发现自己任期落后,立即转为 Follower,并更新任期。它之前接受的未提交日志会被回滚,所有写入以新 Leader 为准。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。