Files
MIT6.824/lecture-07-raft2/7.1.md
2022-01-25 02:41:31 +00:00

6.3 KiB
Raw Permalink Blame History

7.1 日志恢复Log Backup

接6.9 的内容)

我们现在处于这样一个场景

我们假设下一个任期是6。尽管你无法从黑板上确认这一点但是下一个任期号至少是6或者更大。我们同时假设S3在任期6被选为Leader。在某个时刻新Leader S3会发送任期6的第一个AppendEntries RPC来传输任期6的第一个Log这个Log应该在槽位13。

这里的AppendEntries消息实际上有两条因为要发给两个Followers。它们包含了客户端发送给Leader的请求。我们现在想将这个请求复制到所有的Followers上。这里的AppendEntries RPC还包含了prevLogIndex字段和prevLogTerm字段。所以Leader在发送AppendEntries消息时会附带前一个槽位的信息。在我们的场景中prevLogIndex是前一个槽位的位置也就是12prevLogTerm是S3上前一个槽位的任期号也就是5。

这样的AppendEntries消息发送给了Followers。而Followers它们在收到AppendEntries消息时可以知道它们收到了一个带有若干Log条目的消息并且是从槽位13开始。Followers在写入Log之前会检查本地的前一个Log条目是否与Leader发来的有关前一条Log的信息匹配。

所以对于S2 它显然是不匹配的。S2 在槽位12已经有一个条目但是它来自任期4而不是任期5。所以S2将拒绝这个AppendEntries并返回False给Leader。S1在槽位12还没有任何Log所以S1也将拒绝Leader的这个AppendEntries。到目前位置一切都还好。为什么这么说呢因为我们完全不想看到的是S2 把这条新的Log添加在槽位13。因为这样会破坏Raft论文中图2所依赖的归纳特性并且隐藏S2 实际上在槽位12有一条不同的Log的这一事实。

我们不想看到的场景

所以S1和S2都没有接受这条AppendEntries消息所以Leader看到了两个拒绝。

Leader为每个Follower维护了nextIndex。所以它有一个S2的nextIndex还有一个S1的nextIndex。之前没有说明的是如果Leader之前发送的是有关槽位13的Log这意味着Leader对于其他两个服务器的nextIndex都是13。这种情况发生在Leader刚刚当选因为Raft论文的图2规定了nextIndex的初始值是从新任Leader的最后一条日志开始而在我们的场景中对应的就是槽位13.

为了响应Followers返回的拒绝Leader会减小对应的nextIndex。所以它现在减小了两个Followers的nextIndex。这一次Leader发送的AppendEntries消息中prevLogIndex等于11prevLogTerm等于3。同时这次Leader发送的AppendEntries消息包含了prevLogIndex之后的所有条目也就是S3上槽位12和槽位13的Log。

对于S2来说这次收到的AppendEntries消息中prevLogIndex等于11prevLogTerm等于3与自己本地的Log匹配所以S2会接受这个消息。Raft论文中的图2规定如果接受一个AppendEntries消息那么需要首先删除本地相应的Log如果有的话再用AppendEntries中的内容替代本地Log。所以S2会这么做它会删除本地槽位12的记录再添加AppendEntries中的Log条目。这个时候S2的Log与S3保持了一致。

但是S1仍然有问题因为它的槽位11是空的所以它不能匹配这次的AppendEntries。它将再次返回False。而Leader会将S1对应的nextIndex变为11并在AppendEntries消息中带上从槽位11开始之后的Log也就是槽位111213对应的Log。并且带上相应的prevLogIndex10和prevLogTerm3

这次的请求可以被S1接受并得到肯定的返回。现在它们都有了一致的Log。

而Leader在收到了Followers对于AppendEntries的肯定的返回之后它会增加相应的nextIndex到14。

在这里Leader使用了一种备份机制来探测Followers的Log中第一个与Leader的Log相同的位置。在获得位置之后Leader会给Follower发送从这个位置开始的剩余的全部Log。经过这个过程所有节点的Log都可以和Leader保持一致。

重复一个我们之前讨论过的话题或许我们还会再讨论。在刚刚的过程中我们擦除了一些Log条目比如我们刚刚删除了S2中的槽位12的Log。这个位置是任期4的Log。现在的问题是为什么Raft系统可以安全的删除这条记录毕竟我们在删除这条记录时某个相关的客户端请求也随之被丢弃了。

我在上堂课说过这个问题这里的原理是什么呢是的这条Log条目并没有存在于过半服务器中因此无论之前的Leader是谁发送了这条Log它都没有得到过半服务器的认可。因此旧的Leader不可能commit了这条记录也就不可能将它应用到应用程序的状态中进而也就不可能回复给客户端说请求成功了。因为它没有存在于过半服务器中发送这个请求的客户端没有理由认为这个请求被执行了也不可能得到一个回复。因为这里有一条规则就是Leader只会在commit之后回复给客户端。客户端甚至都没有理由相信这个请求被任意服务器收到了。并且Raft论文中的图2说明如果客户端发送请求之后一段时间没有收到回复它应该重新发送请求。所以我们知道不论这个被丢弃的请求是什么我们都没有执行它没有把它包含在任何状态中并且客户端之后会重新发送这个请求。

学生提问前面的过程中为什么总是删除Followers的Log的结尾部分

Robert教授一个备选的答案是Leader有完整的Log所以当Leader收到有关AppendEntries的False返回时它可以发送完整的日志给Follower。如果你刚刚启动系统甚至在一开始就发生了非常反常的事情某个Follower可能会从第一条Log 条目开始恢复然后让Leader发送整个Log记录因为Leader有这些记录。如果有必要的话Leader拥有填充每个节点的日志所需的所有信息。