7.2 选举约束(Election Restriction)

在前面的例子中,我们选择S3作为Leader。现在有个问题是,哪些节点允许成为Leader?

如果你读了Raft论文,那么你就知道答案:为了保证系统的正确性,并非任意节点都可以成为Leader。不是说第一个选举定时器超时了并触发选举的节点,就一定是Leader。Raft对于谁可以成为Leader,谁不能成为Leader是有一些限制的。

为了证明并非任意节点都可以成为Leader,我们这里提出一个例子来证伪。在这个反例中,Raft会选择拥有最长Log记录的节点作为Leader,这个规则或许适用于其他系统,实际上在一些其他设计的系统中的确使用了这样的规则,但是在Raft中,这条规则不适用。所以,我们这里需要研究的问题是:为什么不选择拥有最长Log记录的节点作为Leader?如果我们这么做了的话,我们需要更改Raft中的投票规则,让选民只投票给拥有更长Log记录的节点。

很容易可以展示为什么这是一个错误的观点。我们还是假设我们有3个服务器,现在服务器1(S1)有任期5,6,7的Log,服务器2和服务器3(S2和S3)有任期5,8的Log。

为了避免我们在不可能出现的问题上浪费时间,这里的第一个问题是,这个场景可能出现吗?让我们回退一些时间,在这个时间点S1赢得了选举,现在它的任期号是6。它收到了一个客户端请求,在发出AppendEntries之前,它先将请求存放在自己的Log中,然后它就故障了,所以它没能发出任何AppendEntries消息。

之后它很快就故障重启了,因为它是之前的Leader,所以会有一场新的选举。这次,它又被选为Leader。然后它收到了一个任期7的客户端请求,将这个请求加在本地Log之后,它又故障了。

S1故障之后,我们又有了一次新的选举,这时S1已经关机了,不能再参加选举,这次S2被选为Leader。如果S2当选,而S1还在关机状态,S2会使用什么任期号呢?

明显我们的答案是8(因为之前画出来了),但是为什么任期号是8而不是6呢?尽管没有写在黑板上,但是S1在任期6,7能当选,它必然拥有了过半节点的投票,过半服务器至少包含了S2,S3中的一个节点。如果你去看处理RequestVote的代码和Raft论文的图2,当某个节点为候选人投票时,节点应该将候选人的任期号记录在持久化存储中。所里在这里,S2或者S3或者它们两者都知道任期6和任期7的存在。因此,当S1故障了,它们中至少一个知道当前的任期是8。这里,只有知道了任期8的节点才有可能当选,如果只有一个节点知道,那么这个节点会赢得选举,因为它拥有更高的任期号。如果S2和S3都知道当前任期是8,那么它们两者中的一个会赢得选举。所以,下一个任期必然为8这个事实,依赖于不同任期的过半服务器之间必然有重合这个特点。同时,也依赖任期号会通过RequestVote RPC更新给其他节点,并持久化存储,这样出现故障才不会丢失数据。所以下一个任期号将会是8,S2或者S3会赢得选举。不管是哪一个,新的Leader会继续将客户端请求转换成AppendEntries发给其他节点。所以我们现在有了这么一个场景。

现在我们回到对于这个场景的最初的问题,假设S1重新上线了,并且我们又有了一次新的选举,这时候可以选择S1作为Leader吗?或者说,可以选择拥有最长Log记录的节点作为Leader可以吗?明显,答案是不可以的。

如果S1是Leader,它会通过AppendEntries机制将自己的Log强加给2个Followers,这个我们刚刚(上一节)说过了。如果我们让S1作为Leader,它会发出AppendEntries消息来覆盖S2和S3在任期8的Log,并在S2和S3中写入S1中的任期6和任期7的Log,这样所有的节点的Log才能与S1保持一致。为什么我们不能认可这样的结果呢?

是的,因为S2和S3可以组成过半服务器,所以任期8的Log已经被commit了,对应的请求很可能已经执行了,应用层也很可能发送一个回复给客户端了。所以我们不能删除任期8的Log。因此,S1也就不能成为Leader并将自己的Log强制写入S2和S3。大家都明白了为什么这对于Raft来说是个坏的结果吗?正因为这个原因,我们不能在选举的时候直接选择拥有最长Log记录的节点。当然,最短Log记录的节点也不行。

在Raft论文的5.4.1,Raft有一个稍微复杂的选举限制(Election Restriction)。这个限制要求,在处理别节点发来的RequestVote RPC时,需要做一些检查才能投出赞成票。这里的限制是,节点只能向满足下面条件之一的候选人投出赞成票:

  1. 候选人最后一条Log条目的任期号大于本地最后一条Log条目的任期号;

  2. 或者,候选人最后一条Log条目的任期号等于本地最后一条Log条目的任期号,且候选人的Log记录长度大于等于本地Log记录的长度

回到我们的场景,如果S2收到了S1的RequestVote RPC,因为S1的最后一条Log条目的任期号是7,而S2的最后一条Log条目的任期号是8,两个限制都不满足,所以S2和S3都不会给S1投赞成票。即使S1的选举定时器的超时时间更短,并且先发出了RequestVote请求,除了它自己,没人会给它投票,所以它只能拿到一个选票,不能凑够过半选票。如果S2或者S3成为了候选人,它们中的另一个都会投出赞成票,因为它们最后的任期号一样,并且它们的Log长度大于等于彼此(满足限制2)。所以S2或者S3中的任意一个都会为另一个投票。S1会为它们投票吗?会的,因为S2或者S3最后一个Log条目对应的任期号更大(满足限制1)。

所以在这里,Raft更喜欢拥有更高任期号记录的候选人,或者说更喜欢拥有任期号更高的旧Leader记录的候选人。限制2说明,如果候选人都拥有任期号最高的旧Leader记录,那么Raft更喜欢拥有更多记录的候选人。

最后更新于