# 7.2 选举约束（Election Restriction）

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

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

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

![](/files/-MBWZ6bUFFft4zw2r7nP)

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

![](/files/-MBW_OcVeCVhg2L3q6KY)

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

![](/files/-MBW_wUblqqOEqJnjpuo)

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

![](/files/-MBWaoJSRdRe5NZLD5q6)

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

![](/files/-MBWdJ_lN3OZSailEx6z)

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

![](/files/-MBWhNba-P4tNkgpZ6zo)

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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://mit-public-courses-cn-translatio.gitbook.io/mit6-824/lecture-07-raft2/7.2-xuan-ju-yue-shu-election-restriction.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
