Files
MIT6.824/lecture-06-raft1/6.9-ke-neng-de-yi-chang-qing-kuang.md
2022-01-25 02:41:31 +00:00

47 lines
6.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 6.9 可能的异常情况
一个旧Leader在各种奇怪的场景下故障之后为了恢复系统的一致性一个新任的Leader如何能整理在不同副本上可能已经不一致的Log
这个话题只在Leader故障之后才有意义如果Leader正常运行Raft不太会出现问题。如果Leader正在运行并且在其运行时系统中有过半服务器。Leader只需要告诉FollowersLog该是什么样子。Raft要求Followers必须同意并接收Leader的Log这在Raft论文的图2中有说明。只要Followers还能处理它们就会全盘接收Leader在AppendEntries中发送给它们的内容并加到本地的Log中。之后再收到来自Leader的commit消息在本地执行请求。这里很难出错。
在Raft中当Leader故障了才有可能出错。例如旧的Leader在发送消息的过程中故障了或者新Leader在刚刚当选之后还没来得及做任何操作就故障了。所以这里有一件事情我们非常感兴趣那就是在一系列故障之后Log会是怎样
这里有个例子假设我们有3个服务器S1S2S3我将写出每个服务器的Log每一列对齐之后就是Log的一个槽位。我这里写的值是Log条目对应的任期号而不是Log记录的客户端请求。所以第一列是槽位1第二列是槽位2。所有节点在任期3的时候记录了一个请求在槽位1S2和S3在任期3的时候记录了一个请求在槽位2。在槽位2S1没有任何记录。 
![](<../.gitbook/assets/image (39).png>)
所以,这里的问题是:这种情况可能发生吗?如果可能发生,是怎么发生的?
这种情况是可能发生的。假设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类似于前一个例子。在槽位12S2有一个任期4的请求而S3有一个任期5的请求。在我们分析之前我们需要明白发生了什么会导致这个场景我们需要清楚这个场景是否真的存在因为有些场景不可能存在我们也就没必要考虑它。所以现在的问题是这种场景可能发生吗
![](<../.gitbook/assets/image (40).png>)
这种场景是可能发生的。我们假设S2在槽位12时是任期4的新Leader它收到了来自客户端的请求将这个请求加到了自己的Log中然后就故障了。
![](<../.gitbook/assets/image (41).png>)
因为Leader故障了我们需要一次新的选举。我们来看哪个服务器可以被选为新的Leader。这里S3可能被选上因为它只需要从过半服务器获得认可投票而在这个场景下过半服务器就是S1和S3。所以S3可能被选为任期5的新Leader之后收到了来自客户端的请求将这个请求加到自己的Log中然后故障了。之后就到了例子中的场景了。
![](<../.gitbook/assets/image (42).png>)
因为可能发生Raft必须能够处理这种场景。在我们讨论Raft会如何做之前我们必须了解怎样才是一种可接受的结果。大概看一眼这个图我们知道在槽位10的Log3个副本都有记录它可能已经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了。
我们会在下一节课继续这个话题。