先记住这个答案
Raft 中节点超时后进入选举会先自增任期,若它处于网络分区孤岛,则永远得不到多数票,超时后再次自增,任期可被暴力抬到很高。分区恢复时该节点的高任期会迫使当前 Leader 下台,触发无意义的重选。PreVote 让候选者先发 PreVote 请求,只有获得多数派同意后才正式递增任期并发动选举。孤立节点无法通过多数认可,任期保持不变,分区恢复后不会干扰现有集群,从而解决任期膨胀引发的重复选主问题。
- PreVote 通过多数认可门控任期增长。
- 它避免孤立节点分区恢复后强制重选。
- 不改变日志比较规则,多一轮 RPC 成本。
预投票如何阻断任期自增循环
标准 Raft 选主前,候选者将 currentTerm 加一,再发送 RequestVote。如果该节点处于一个少于半数的分区,它发出的请求无法得到多数响应,超时后会再次将 currentTerm 加一,循环往复。术语称为孤岛自增,最终节点可能积累几百甚至上千的任期号,而集群主分区却停留在最初的任期。
PreVote 将“询问”和“自增”解耦:候选节点先广播 PreVote,消息中携带它打算使用的任期号(当前任期+1)。接收方根据自身的任期和日志状态正常决定是否授予预投票。只有收获过半预投票节点后,候选者才将自己的实际任期置为新值,并继续正式选举。孤岛节点请求永远止步于 PreVote 阶段,任期自然无法升高。
五节点网络割裂模拟
集群 {n1..n5},n1 是 Leader,网络故障后 n1,n4,n5 与 n2,n3 分开。n2 先超时,在无 PreVote 的旧实现中,n2 将任期升到2,获得 n3 的投票,加上自己的一票共两票,不足三票;之后 n2 反复超时,任期升至10、50甚至100。恢复后 n2 在选举超时后发送携带任期100的RequestVote到达 n1,n1 发现更大任期便退回 Follower,整个集群不得不重新选 Leader,而 n2 日志可能并不新。
启用 PreVote 后,n2 以任期2发起预投票,仅 n3 同意,两票不及阈值,n2 实际任期保持1。重复超时后依然无法得到多数,其任期始终为1。恢复后 n2 与 n1 通讯时无任期差异,n1 继续当 Leader,集群无需重新选主,只有 n2 的网络包恢复后立即同步日志即可收敛。
PreVote 的依赖与局限
PreVote 要求多数派的“同意”是真实可达的。如果分区使得两个子区都不足一半,例如恰好各占一半,则没有任何候选者能获得多数 PreVote,整个系统将变得不可用。它不能提高分区时的可用性,只能避免少数派对主流集群的任期污染。
此外,PreVote 会带来额外的一轮 RPC 开销,使正常选主延迟增加一个网络往返。而它并未改变投票规则——接收方仍然要比较日志新旧,日志落后的节点无论如何不会被授票。这意味着 PreVote 解决的是任期抖动的“工程”问题,而不是选举安全性的理论缺口。
容易答错的地方
- PreVote 能避免所有不必要的选举?
- 错误。PreVote 只消除因任期异常抬高而强制的重选,但Leader宕机或心跳超时仍会触发正常选举;PreVote 本身不抑制这些情况,否则无法产生新 Leader。
- PreVote 后节点不会失败?
- 错误。孤立节点在 PreVote 下虽然任期不变,但它同样无法获得读写请求,其日志持续落后。恢复后若该节点落后过多,仍可能不会被选为 Leader,这由选举安全保证。
面试官还会怎么问?
PreVote 能否完全防止脑裂?
不能。PreVote 只处理任期膨胀引发的非必要重选,真正的脑裂(如旧主未退位时日志分叉)需要配合租约过期和心跳超时才能让旧主认为自己失效。PreVote 不解决两个分区各自选举成功的问题,只保证少数派无法通过抬任期来干扰多数派。
在 PreVote 阶段,接收方会更新自己的任期吗?
不会。PreVote 消息中的任期比接收方低时直接忽略,比接收方高也不会立即更新自己的任期,因为这只是预询。只有正式 RequestVote 或心跳带有更高任期时,节点才会更新本地任期。这是为了避免预投票干扰当前领导者。
PreVote 对日志复制过程有影响吗?
几乎无影响。PreVote 只发生在选举前,一旦正式成为 Leader,日志复制流程与标准 Raft 完全相同。它甚至不影响投票的日志比较规则,只是多了预投票的门槛。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。