Raft 选主问题——及时更新 currentTerm
Raft 中的 term 相当于 逻辑时钟,Raft 论文里要求:
当一个 Raft 实例在 RPC Request/Response 中看到比自己 currentTerm 更大的 term 时需要立即更新 currentTerm 并转为 follower。
不及时更新 currentTerm 的会有诸多问题,
我们考虑一种因为 term 更新不及时导致的选举死循环问题,
考虑一种一个五节点的 Raft 集群:
- p3 是 term 408 的 leader
- p4 发起 term 409 的选举,p2、p5 投票给 p4
- p4 当选 term 409 的 leader 但是马上被网络隔离
此时 p3 还以为当前 term 是 408,
继续给 p2、p5 发日志。
p2、p5 拒绝它的日志,
但是发给 p3 的 RPC Response 没带自己的 term(此时值为 409) 或者
p3 没有根据 RPC Response 中的 term 更新自己的 currentTerm 和 role。
如果系统实现了 prevote,而且 p3 的日志比 p2、p5 新,
那么 p2、p5 的 prevote 会一直失败,p2、p5 的 prevote 请求失败了所以他们的 term 不会进一步增长,也无法发起正式选举;
另一方面 p3 不会更新自己的 term 且一直以为自己还是 term 408 的 leader,
但是 p2、p5 不认它的日志,这时候……emm……整个集群就悲剧了。
另一个涉及到 currentTerm 字段跟新和 Raft 选主的问题是:
在一个实现了 prevote 的 Raft 集群中,节点要不要根据 prevote RPC 中的 term 信息来跟新 currentTerm 字段?
或者如果不更新会有什么问题?
考虑一下场景,还是在一个五节点的集群中:
- p1、p2 两节点被网络隔离
- p3、p4、p5 三个节点之间发起选举,三节点的 currentTerm 字段分别为 116、116、115 且 p5 上的日志比 p3、p4 新
- p5 不会再 prevote 中投票给 p3、p4,因为它的日志更新
- p3、p4 也不会投票给 p5 因为 p5 的 term 比他们小
如果 Raft 节点不根据 prevote 字段中的 term 跟新自己的 currentTerm 的话,
在上述的场景中三个节点将永远也无法选举出 leader。
以上我们讨论的两个问题都涉及到 Raft 的 prevote 机制。
关于 prevote 还有一个比较重要的问题。
我们知道在正式的 vote 请求中,
一个 Raft 实例需要先给自己的 currentTerm 加一。
那么 prevote 前也需要递增 currentTerm 吗?
我之前一直认为可以这么做:只要在 prevote 中递增 currentTerm就可以了,
在正式的 vote 中就不需要再递增 currentTerm了。
这种做法一个明显的弊端是如果系统一直 prevote 失败,
那 currentTerm 也会一直递增,
而 prevote 目的之一就是要用来防止 currentTerm 的无序递增。
最近读到的一篇文章中,
这篇文章让我想到以上做法会产生的一个严重问题——选举死循环,考虑一个三节点的集群:
- p1 和 p2、p3 单向隔离,既 p1 可以发消息给 p2、p3 但是 p2、p3 无法给 p1 发消息
- p2 是 leader,因为 p1 总是收不到 leader 心跳所以发起 prevote
因为 p2 总是收不到 leader 心跳他会不停的发起 prevote,
如果我们在 prevote 时就递增 currentTerm 那么 Raft 集群中的 term 不停的递增,
p2、p3 因为 term 递增而陷入无限制的 leader 选举中。
综上,我们可以认为在 prevote 中不应该递增 currentTerm。