raft_guide理解与翻译

本文深入解析raft算法,重点讲解figure2中的RPC行为和不变量,强调必须严格遵守规则,尤其是关于选举超时、RPC处理和term管理的复杂性。文章揭示了实现过程中的常见误解和陷阱,包括活锁、不正确的RPChandler和term混乱的处理策略。
摘要由CSDN通过智能技术生成

关于raft算法的指导(6.824 guide 自己的翻译 + 添加部分理解)

最关键点是 figure 2 是必须满足,而不是最好满足,也就是must 满足而不是should 满足。

任何分布式共识算法的细节是最重要也是最难的。当所有的服务器、网络等外部因素都是没有错误的情况下,共识算法都是很好理解的,但是实际的应用中,算法必须考虑到每一个可能发生的错误,而这里面的细节就是相当复杂的。

实现Raft

figure 2指定了 Raft 服务器之间的 RPC 的行为,给出了服务器必须维护的各种不变量,并指定了何时应发生某些操作。我们将在本文的其余部分详细讨论figure 2。必须严格遵守。

最关键点是 figure 2 是必须满足,而不是最好满足,也就是must 满足而不是should 满足。例如,初步读完论文后,你可能认为 任何服务器收到AppendEntries RPC 或者 RequestVote RPC后都会重置election timeout定时器。但是实际上文章中是这样准确描述的:

如果follower 在 选举超时 时间内没有收到AppendEntries RPC 或者 没有 同意投票给另外一个candidate,那么这个follower 就开启新的选举。

显然原文的内容是准确的,当收到RequestVote RPC后,follower并不会无条件的重置选举超时时间,而是只有同意给到来的RPC投票时才会重置。例如,如果到来的RPC的term小于currentTerm,那么就不会投票,并且也不会重置选举超时时间。

一些实现上的细节

这里会举几个可能会让大伙困惑的例子。初读论文,有人可能会把 没有log entry的心跳 AppendEntries RPC与有log entry的RPC分开处理。对于前者,服务器收到心跳后直接重置 选举超时时间,然后返回true。这是错误的,因为心跳RPC的作用不仅仅时让follwer重置选举时间,它还要根据Leader的prelogIndex来判断当前sever的log是否匹配上了,如果没有匹配上,那么就要返回false。不然一味的返回true,会让Leader误以为log entry已经复制到大部分的机器上去了。

在完成上面的要求后,还有另外一个问题。例如,有的人会在 prelogIndex 和 prelogterm 都正确匹配后,将server这个log后面所有的log entries全部截断,然后添加新的log。 这是不对的,看看原文的说法:

如果存在任何的entry冲突(例如,相同的index 但是不同的term),那么就这个entry和后面的所有的entry全部删除

原文中有一个假设,也就是存在冲突才会全部删除。因为如果有的心跳RPC由于网络等问题,时过时的心跳RPC,但是有没有错误,这时如果我们截断后面的entry,会导致丧失了很多entry,而leader不知道。此外如果此时RPC args中的字段匹配,那么就需要继续往后面遍历,直到找到不匹配的,或者到任何一个entries最后,然后进行添加以及返回。

Debugging Raft

在调试的过程,通常有四个主要的bug来源:活锁(空转),不正确的RPC handler处理,没有遵循规则,以及term混乱。此外死锁也是一个问题之一,可以将所有的获取锁的过程 log出来,就可以找到。

活锁 (空转)

活锁就是Ratf中的所有server服务器都在运转,但是要么一直没有leader被选出(我有遇到过),要么leader一被选出就马上退位,没有进行实质上的操作。有很多原因会导致这个问题,这里举几个例子:

  • 需要正确的重置选取超时的时间。有且仅有如下的三种情况需要重置选举超时时间:a) 获得当前leader的AppendEntries RPC(注意,如果term 过期的话,不会重置,这不是来自自己认为的当前的leader);b) 服务器正在开始选举;c) 服务器同意了 requestRPC投票请求。最后一点在前面强调过。

  • 正确知道何时开始一个选举。特别是如果一个服务器正在选举,但是选举时间太长(可能是RPC网络出现问题),选举超时又一次触发,那么就要开始新的选举了。

  • 确保满足 figure 2 中的这条规则:

    在所有的RPC 的请求或回复中,当term > currenTerm,也就是一旦传来的term大于(注意是严格大于)自己当前的term,那么就立马转变成follewer,并且同步自己的term,同时重置votedfor (踩过坑)

不正确的 RPC handlers

  • 如果当handler的某一个步骤决定了要返回false,那么就立刻返回,不要执行后面的动作
  • 如果收到的AppendEntries RPCprevlogIndexl超过了自己的log长度,那么就当作自己有这个logindex的log但是他们的term不一样,所以就是要返回false,让leader继续减少nextIndex
  • 记得对心跳AppendRpc 也要进行上面的检查
  • 图二中AppendEntries RPC中的 第5条建议是重要的,注意这里面的min。commitIndex更新的位置除了与Leadercommit有关外,还和这个RPC发送的最新logs有关系,不能认为此时的commitindex应该要么是leadercommit 要么是服务器的最后一个log。这是因为如果leader发送的条目 在本server中能够完全匹配,但是server中后续的一些log是与leader不同的,而leadercommit刚好又覆盖到了这些log,如果这些log 被提交那么就出现了错误,所以这些log不能够commit。(踩坑,我直接使用min(Leadercommit, len(rf.logs)出错 ))
  • 记得准确的判断 log entry 最新的条件,不要偷懒只判断index或者长度,term也是很重要的

不遵循相关规则

注意相关的rules:

  • 一旦 commitIdex > lastApplied 就可以 去执行中间的log,当然 也不不需要立刻执行。最好有一个专门的applier 处理器单独使用一个协程来处理,处理时要用锁锁上。(可能只需要在读取的时候锁上,应用的时候应该不需要锁)
  • 要么确保 定期的检查 commitIdex > lastApplied ,要么在commitindex更新后去检查。(感觉前者实现更加简单,就是循环时间需要再看看)
  • 如果Leader发送出RPC后被False,并且不是因为日志冲突,也就是出现了reply中的term 大于 currentterm,leader的term过时,那么leader立马下台,并且不要改变nextIndex,不然可能会跟自己再次当选重置nextIndex有race的风险。(感觉这条用处不大,我统统用大锁)
  • 在本次任期内的log没有正确的commit的时候,leader无法将之前的log认为commit,也就是刚上任的leader无法确保之前任期的term有没有commit,只有在自己任期中有log能够commit了,才可以准确覆盖之前的log。(Leader只能够知道自己当前的term是最新的,但是无法知道前一个term是否也是最新的,详情可以参考我记录的Raft笔记)

此外 还有一个普遍令人困惑的点是关于nextIndexmatchIndex 之间的区别。 我一度认为(现在暂时还这么认为,但是后面可能会改变)matchIndex = nextIndex - 1,所以matchIndex 没有存在的必要,所以可以不用实现都可以。但是这是不安全的。尽管两个的更新会最终处于一个固定的差值,但是两者的目的是完全不同的。

nextIndex是Leader对follower具有相同前缀log的猜测是很乐观的,甚至初始化就设置为自己最后一个log的后面一位。 发送log 的时候 需要根据nextIndex 来发送。

matchIndex是为了安全考虑,是Leader对follower具有相同前缀log的保守估计,matchIndex 决定了commitIndex,所以不能激进,要确保正确。这也就是为什么matchIndex最开始初始化为-1。判断一个log是否被复制到大部分的server中的时候用matchIndex。

Term 混乱

上述内容以及图二中很好的说明了,当收到了RPC回复中有旧的term时要做什么。但是一个比较严重的问题是,当收到旧的RPC要做什么? 注意这里旧term 与 旧RPC 是不一样的。 旧RPC指的是在当前任期内收到 之前任期发送的RPC请求,也就是currentTerm > 发送RPC时参数中的term,此时简单的做法是直接返回,不用管他。

同样的,类似的问题可能出现在收到response后对matchIndex 的更新上。在收到true的respons后,一个可能的操作是 matchIndex = nextIndex - 1, or matchIndex = len(log),但是这是可能是错误的,因为在接收和发送RPC之间,服务器可能会有其他的更新操作,所以我们不能直接的使用服务器当前的字段来更新,而应该用我们发送RPC时传入的参数来进行更新相关的操作。

上面都是一类问题,也就是用RPC参数来进行判断与更新,而不是利用当前的参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值