# 7.2 选举约束（Election Restriction）

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

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

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

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBWZ6bUFFft4zw2r7nP%2Fimage.png?alt=media\&token=ba1fe57d-e7bb-4560-aeed-deaffac8c855)

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

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBW_OcVeCVhg2L3q6KY%2Fimage.png?alt=media\&token=b0c9f024-4b50-4dd4-bd3d-6e6976af525f)

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

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBW_wUblqqOEqJnjpuo%2Fimage.png?alt=media\&token=d6768875-6fc1-48a1-ad09-8104e0eb5a39)

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

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBWaoJSRdRe5NZLD5q6%2Fimage.png?alt=media\&token=73550c13-fda9-4884-b7b5-b431074482b1)

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发给其他节点。所以我们现在有了这么一个场景。

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBWdJ_lN3OZSailEx6z%2Fimage.png?alt=media\&token=f0282299-a149-4720-acb2-b6a40c248c0b)

现在我们回到对于这个场景的最初的问题，假设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记录的长度

![](https://2933519158-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MAkokVMtbC7djI1pgSw%2F-MBU-cLGzrm5ZAw-M4Lb%2F-MBWhNba-P4tNkgpZ6zo%2Fimage.png?alt=media\&token=2f04ff3d-9499-49ff-b8d9-b032b1005b43)

回到我们的场景，如果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更喜欢拥有更多记录的候选人。
