mit6.824 lab2 RAFT node persist 要点记录
程序员文章站
2022-07-12 16:54:53
...
我的6.824 课程 Pass,欢迎STAR
- commitIndex是volatile的,不需要persist。OMG,这一点会影响之后的一些操作
- 需要考虑那些状态需要persist,一个node恢复以后,只恢复必要的自身相关的状态就好,然后重新进入循环
- 既然需要重新进入循环,那样的话对于leader,就不需要没有commit的多余log了。 这样不行啊,前一个term的log可能已经完成replicate的过程了,但是还么有进行commit操作,这时需要保留到下一个term,然后通过新命令或者no-op command进行commit
- AER中可以添加一些额外的字段帮助leader快速的back up,比如lastLogIndex,然后leader直接从lastLogIndex进行back up(仍然需要使用跨term的方法进行再优化)。commitIndex、TermOfArgsPrevLogIndex,后者由于log backup 的过程中的优化。
- **对于leader刚开始时,commitIndex当有新的命令过来时再进行更新,这也是figure8所要求的:不通过count replica 的方式对上一个term的log进行commit。**对于follower只有在leader的AE过来时,才更新commit。
- 论文对follower的log缺失(AER return false)back up 给的优化办法是一次跳到前一个term的log,为什么不直接让AER携带这些信息呢,应该会更快。在AER中返回lastLogIndex,当lastLogIndex小于prevlogIndex的时候,设置prevLogIndex为lastLogIndex
- 对于AE的prevLogIndex大于当前的commitIndex这种情况允许发生,会设置AER结构的commitIndex。AER会返回true,在AER_handler中会处理这种情况,会把prevLogIndex设置为commitIndex重新backup。
- voteFor也需要persist???。实际上并没有用
- leader对于term的快速定位,当AER为因目标index处的term不匹配而返回false的时候,可以让follower返回对应的prevlogindex位置term(TermOfArgsPrevLogIndex)以便leader快速定位follower的日志的准确位置。可以在leader启动的时候,设置索引,定位每一个term的开始位置。当AER因term不匹配的问题返回不匹配位置的log时,可以定位到最近的logindex。
- 同时对于commitIndex的处理,一个节点commitIndex以前的log是不允许修改的,但也不是说AE的PrevLogIndex到了小于commitIndex的位置时就一定要求leader turn to follower。尽量不去碰commitIndex,避免修改以前的东西。
- commitIndex如果不进行持久化,会出现重复apply的问题。如果恢复的时候带上commitIndex就不会出现这样的情况
- 注释了ble2C中的延迟RPC返回代码,结果也一样会出错,原因在于leader查找一致点的过程太慢了,整个过程一点都不高效,当follower收到AE但无法成功时,可以返回不匹配的上一个term与index,这样leader内部相当于又执行了一下。
- 在命令返回的计数上,由于并发的存在需要一定的方法来合理的计数,一种方法是将命令的index和leader内部的一个slice对应,当slice的一个index的计数大于majority的时候,把leaderCommit到该index的log全部commit。也不好。大道至简,每次AERreturn true就做一次检查呗,干嘛搞那么麻烦,最后的解决方案是采取对 matchIndex 的copy进行sort,然后找到len(rf.matchIndex)/2+1位置的index,比原来的commitIndex大的,就选择commit。
- rf.nextIndex和matchIndex的区别???在我的实现中两者的差别并不大
- 并发的时候,并不是简单的加锁就可以解决所有问题,在合适的时间加合适的锁才能得到正确的结果。再次吐槽,并发编程真的不是简简单单就能完成的,这东西需要经验的积累。
- 编写过程中,一些并发访问的地方,let’s say 连续两行需要使用同一个值,而该值被并发修改,这时就存在前一行和后一行使用的值不一致。这时就应该用变量记录下当前值,在之后的开发中使用变量,而不是两行都直接访问。
- follower接受到AE的时候,**应该在收到AE的log以后进行判断,如果本地最后一个log的term小于该AE的log的term值的话,就及时的删掉之后的log。**避免出现index:13-20 的term是9,而21-45的term是5这样的导致出错的情况的发生。
TODO
- 优化整个过程中的RPC调用:相比于课程页面上给出的例子,我的Pass显得RPC的数量高很多,优化思路,待思考。
- preVote机制实现:Raft的PreVote实现机制
- term索引通过二分的方法而不是通过遍历的方法进行建立。一万条log采用遍历的方法需要1ms左右,相对于Leader的周期1000ms来说还是有点耗时的。
other Problems
- 当leader收到其他大多数node的AppendEntryReply(AER)的成功时,也就是大多数成功replicate以后,标记当前log(s)为commit状态,更新commitIndex,然后apply到状态机中,我的实现是在下一次的HeartBeat(HB)或者AE分发的时候,follower才更新刚才的log的状态为commit,如果在leader更新完commitIndex但没有完成向follower发送确认时,leader failed(意外down掉或者收到更高的term 然后turn to follower),这和figure8的情况有点类似,这次的问题是:原来的leader已经把那个log commit掉了,后来的leader又修改了那个index对应的log,因为commit以后不能在修改,这样原来的leader 恢复以后,没有办法接受新leader的log,也没有办法在成为leader,就卡在这了,怎么破? 仔细考虑一下,可以发现上有情况在leaderElection正常运行时(即只有一个leader)并且避免了figure8的情况的时候不可能出现
- 发现最后的测试过程中收到了nil命令??:因为rf.log索引访问越了界,而golang的slice对这样的情况并不会报index out of range,如下:
lenArr=len(Arr); //say lenArr=10
tmpSlice=Arr[10:12] //as usual there should be a runtime error
fmt.Printf("%v", tmpSlice) //actually it runs without warnings or errors. print [0, 0], heartbreaking
- node down掉以后,在重新变成leader的时候,因为leaderCommit为0,所以会将commit过的log再次commit。这样会不会有什么问题???:不会,状态机来记录commit的内容,如果发生重复就不apply
lab2C中的并发同步是最让我难受的了,难怪网上有人的做法直接在RV和AE相关的里面全部加上锁,最后离开时释放,直接将游戏难度从abnormal拉到了easy。
并发编程真的好蛋疼啊。。。但完成以后----