MIT 6.824: Distributed Systems- 实现Raft Lab2C

接上文《MIT 6.824: Distributed Systems- 实现Raft Lab2B》,Lab2C包含2个主要任务:

  • 当currentTerm、voteFor、log[]更新后,调用persister将它们持久化下来,因为这3个状态是要求持久化的。
  • 优化nextIndex的回退性能,即appendEntries被拒时,论文默认反复减1回退重试,导致耗费很长时间才能找到同步位置,优化后可以一次性跳过更多的index,减少RPC往复。

已通过MIT单元测试。

Lab2C实现

实现persist持久化函数非常简单,只需要调用作业提供的persister把3个持久化状态写进去就行。

Lab2C坑了半天,有一个Case一直单测不过:

go test -run TestFigure8Unreliable2C

分析是日志同步协调太慢,导致单测超时失败,发现这个Case通过制造网络分区产生了2个leader,然后不断的向2个leader都写入日志,导致产生了2个很长的歧义链。当网络分区恢复后,2个歧义链之间日志同步过程,会因为不断的prevLogTerm冲突导致nextIndex不断回退,因为每次只回退1位,进而耗时非常长。

另外发现Lab2B中的一个case也是同样道理,存在一定几率超时,原因也是歧义链的冲突处理耗时过长:

go test -run TestBackup2B

一开始怀疑是不是从Lab2B就写Bug了,结果发现的确不是bug,而是冲突处理性能问题。

持久化函数

只要在所有修改持久化状态之后调用persist即可。

appendEntries应答

Reply增加2个字段,用于leader和follower协调同步位置,优化回退性能。

appendEntries服务端

重点是conflictIndex和conflictTerm的理解和处理,看这篇MIT的总结:

https://thesquareplanet.com/blog/students-guide-to-raft/#an-aside-on-optimizations、

appendEntries客户端

在success==false情况下,处理nextIndex回退时遵从了优化后的逻辑。

总结

lab2C只是在lab2B基础上,把持久化状态进行了persist存储,另外对日志同步性能提出了更高要求,因为它会制造网络分区形成2个leader然后向2个leader同时写入大量日志,造成2个很长的歧义日志,然而默认的论文实现每次回退1个下标进行重试是无法通过单测的。

  • 回退优化原理:如果follower日志比leader短,那么leader可以直接从follower末尾的index开始尝试传日志,只有这样follower日志才不会出现空槽的情况;否则follower在prevLogIndex位置冲突的term,如果在leader中也有这个term的日志,则从leader日志中该term最后一次出现的位置开始尝试同步,避免给follower错过这个term的任何一条日志;如果冲突term在leader里压根不在,则从follower日志该term首次出现的下标开始同步,因为leader压根没有这个term的日志,相当于对follower截断。
  • leader可能收到旧的appendEntries RPC应答,因此leader收到RPC应答时重新加锁后,应该注意检查currentTerm是否和RPC时的term一样,另外也不应该对nextIndex直接做-=1这样的相对计算(因为旧RPC应答之前可能新RPC已经应答并且修改了nextIndex),而是应该用RPC时的prevLogIndex等信息做绝对计算,这样是不会有问题的。

多看看MIT对6.824常见问题的总结:https://thesquareplanet.com/blog/students-guide-to-raft/#an-aside-on-optimizations

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~

MIT 6.824: Distributed Systems- 实现Raft Lab2C》有15个想法

  1. Pingback引用通告: MIT 6.824: Distributed Systems- 实现Raft Lab3A | 鱼儿的博客

  2. 王祺

    博主你好,我跑的这个版本的代码,应该是lab2C的完整版吧,https://github.com/owenliang/mit-6.824/blob/b88f6a745f18ef4dd9e4d0f7293cfb81fd49e064/src/raft/raft.go, 我跑了几十次,大概有4 5次过不去Test (2A): election after network failure … 和 Test (2C): Figure 8 (unreliable) …晕死。
    另外,话说这篇博客appendEntries客户端的72行,
    if reply.Success { // 同步日志成功
    rf.nextIndex[id] = args1.PrevLogIndex + len(args1.Entries) + 1
    rf.matchIndex[id] = rf.nextIndex[id] – 1
    如果收到一个延迟返回的短日志老心跳包的reply.success,会不会把rf.nextIndex[id]给缩小了???

    回复
  3. 心安的说

    我想请问一下如何处理这种情况呢?3个服务器,其中一个断线重连回来,followerA断线期间在选举,导致term非常大,然后回来以后leaderB发送心跳信息发现了比自己的term大的情况,然后转换为follower,重新发起选举。原来的followerA的较大的概率先超时(leader发现的时候A已经过了一会),会先发起选举,旧的leader发起选举的时候,A已经头投给自己了,不能投给B,然后B选举不成功,B的lastPrevTerm大于A,肯定不投给A,两个都选举失败。然后还是大概率A先超时,先发起选举。请问这种情况应该怎么优化呢?

    回复
      1. 匿名

        这就是随机化选举超时时间的意义,由于选举超时时间是随机的,所以在几轮内就大概率会出现比A更早超时的其他follower,从而继续运行

        回复
  4. mayweather

    总结中的“避免给follower错过这个term的任何一条日志”应该是“避免给follower错过prevLogIndex到这个term的上一条已经同步的日志之间的其他term的日志”吧,因为follower在回退时,直接退回到confictIndex第一次出现的位置,可能会误伤到其他term的日志

    回复
  5. 匿名

    谢谢大佬!!!TestFigure8Unreliable2C真的很烦,明明程序没有错,排查了半天,看了半天日志,结合大佬说的,懂了!!

    回复

yuer进行回复 取消回复

您的电子邮箱地址不会被公开。