# 6.9 可能的异常情况

一个旧Leader在各种奇怪的场景下故障之后，为了恢复系统的一致性，一个新任的Leader如何能整理在不同副本上可能已经不一致的Log？

这个话题只在Leader故障之后才有意义，如果Leader正常运行，Raft不太会出现问题。如果Leader正在运行，并且在其运行时，系统中有过半服务器。Leader只需要告诉Followers，Log该是什么样子。Raft要求Followers必须同意并接收Leader的Log，这在Raft论文的图2中有说明。只要Followers还能处理，它们就会全盘接收Leader在AppendEntries中发送给它们的内容，并加到本地的Log中。之后再收到来自Leader的commit消息，在本地执行请求。这里很难出错。

在Raft中，当Leader故障了才有可能出错。例如，旧的Leader在发送消息的过程中故障了，或者新Leader在刚刚当选之后，还没来得及做任何操作就故障了。所以这里有一件事情我们非常感兴趣，那就是在一系列故障之后，Log会是怎样？

这里有个例子，假设我们有3个服务器（S1，S2，S3），我将写出每个服务器的Log，每一列对齐之后就是Log的一个槽位。我这里写的值是Log条目对应的任期号，而不是Log记录的客户端请求。所以第一列是槽位1，第二列是槽位2。所有节点在任期3的时候记录了一个请求在槽位1，S2和S3在任期3的时候记录了一个请求在槽位2。在槽位2，S1没有任何记录。&#x20;

![](/files/-MBQw0qVBbS6oFTd7rZb)

所以，这里的问题是：这种情况可能发生吗？如果可能发生，是怎么发生的？

这种情况是可能发生的。假设S3是任期3的Leader，它收到了一个客户端请求，之后发送给其他服务器。其他服务器收到了相应的AppendEntries消息，并添加Log到本地，这是槽位1的情况。之后，S3从客户端收到了第二个请求，它还是需要将这个请求发送给其他服务器。但是这里有三种情况：

* 发送给S1的消息丢了
* S1当时已经关机了
* S3在向S2发送完AppendEntries之后，在向S1发送AppendEntries之前故障了

现在，只有S2和S3有槽位2的Log。Leader在发送AppendEntries消息之前，总是会将新的请求加到自己的Log中（所以S3有Log），而现在AppendEntries RPC只送到了S2（所以S2有Log）。这是不同节点之间Log不一样的一种最简单的场景。我们现在知道了它是如何发生的。

如果现任Leader S3故障了，首先我们需要新的选举，之后某个节点会被选为新的Leader。接下来会发生两件事情：

* 新的Leader需要认识到，槽位2的请求可能已经commit了，从而不能丢弃。
* 新的Leader需要确保S1在槽位2记录与其他节点完全一样的请求。

这里还有另外一个例子需要考虑。还是3个服务器，这次我会给Log的槽位加上数字，这样更方便我们后面说明。我们这里有槽位10、11、12、13。槽位10和槽位11类似于前一个例子。在槽位12，S2有一个任期4的请求，而S3有一个任期5的请求。在我们分析之前，我们需要明白，发生了什么会导致这个场景？我们需要清楚这个场景是否真的存在，因为有些场景不可能存在我们也就没必要考虑它。所以现在的问题是，这种场景可能发生吗？

![](/files/-MBR-3wdLZal8v1Qwl5G)

这种场景是可能发生的。我们假设S2在槽位12时，是任期4的新Leader，它收到了来自客户端的请求，将这个请求加到了自己的Log中，然后就故障了。

![](/files/-MBR-jCKX-LMa8GDdL8u)

因为Leader故障了，我们需要一次新的选举。我们来看哪个服务器可以被选为新的Leader。这里S3可能被选上，因为它只需要从过半服务器获得认可投票，而在这个场景下，过半服务器就是S1和S3。所以S3可能被选为任期5的新Leader，之后收到了来自客户端的请求，将这个请求加到自己的Log中，然后故障了。之后就到了例子中的场景了。

![](/files/-MBR0d1KijXJ9ROLGxIj)

因为可能发生，Raft必须能够处理这种场景。在我们讨论Raft会如何做之前，我们必须了解，怎样才是一种可接受的结果。大概看一眼这个图，我们知道在槽位10的Log，3个副本都有记录，它可能已经commit了，所以我们不能丢弃它。类似的在槽位11的Log，因为它被过半服务器记录了，它也可能commit了，所以我们也不能丢弃它。在槽位12记录的两个Log（分别是任期4和任期5），都没有被commit，所以Raft可以丢弃它们。这里没有要求必须都丢弃它们，但是至少需要丢弃一个Log，因为最终你还是要保持多个副本之间的Log一致。

> 学生提问：槽位10和11的请求必然执行成功了吗？
>
> Robert教授：对于槽位11，甚至对于槽位10，我们不能从Log中看出来Leader在故障之前到底执行到了哪一步。有一种可能是Leader在发送完AppendEntries之后就立刻故障了，所以Leader没能收到其他副本的确认，相应的请求也就不会commit，进而也就不会执行这个请求，所以它也就不会发出增加了的commit值，其他副本也就可能也没有执行这个请求。所以完全可能槽位10和槽位11的请求没有被执行。如果Raft能知道这些，那么丢弃槽位10和槽位11的Log也是合法的，因为它们没有被commit。但是从Log上看，没有办法否认这些请求被commit了。换句话说，这些请求可能commit了。所以Raft必须认为它们已经被commit了，因为完全有可能，Leader是在对这些请求走完完整流程之后再故障。所以这里，我们不能排除Leader已经返回响应给客户端的可能性，只要这种可能性存在，我们就不能将槽位10和槽位11的Log丢弃，因为客户端可能已经知道了这个请求被执行了。所以我们必须假设这些请求被commit了。

我们会在下一节课继续这个话题。


---

# 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-06-raft1/6.9-ke-neng-de-yi-chang-qing-kuang.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.
