写完Lab2B后搞大论文,又回来做的Lab2C
在Lab2B基础上简单实现persist后无法通过测试样例Test (2C): Figure 8
。根据raft图2的最后一个提示,更新commitIndex
到i
的时候要校验log[i-1].Term == currentTerm
。这个原理(可能包括raft整个文章)要重新整理下。
但是接下来TestFigure8Unreliable2C
卡了很长时间(可能断断续续有一个礼拜了),就是所谓的样例Test (2C): Figure 8 (unreliable) ...
。这里把原文细节(重点是Fig8的细节和原文5.4节的逻辑)和Students’ Guide to Raft又过了一下,也改进了一些点。
- 收到
AppendEntries
包后,就算对上了PrevLogIndex
,也要:- 加标志位,判断该Term更新过至少一次的话,是不是过时的AppendEntries包,使得log变短。
- 是否甚至是其他Term的过时包:
- 如果
PrevLogIndex
加args.Entries
少等于已有log,只检查最后的args.Entries
对应位置条目的Term,与已有log不一致则替换 - 如果
PrevLogIndex
加args.Entries
多于已有log,直接替换
- 如果
- 收到的是
AppendEntries
包,commitIndex更新的大小不能高于本次args.PrevLogIndex + len(args.Entries)
- 日志对齐函数中,取消过时包:
reply.Success == true
,matchIndex
不能降低- (
reply.Success == false
的逻辑没注意,下面就是这个问题)
- 选举计票优化了下,不用等所有票投完,只有赞成票和拒绝票都不够majority才继续
cond.Wait()
,同时要判断当前还是Candidate、Term没变
但还是不行,也就是像解决Lab2B问题时那样不依赖调试是不行了,然后搜了一下TestFigure8Unreliable2C
,似乎个人的问题都不同。不过还是得到一些提示,虽然已经开始阅读甚至改造config.go
和test_test.go
了,但是有个githubIssue总结得确实好一点:
TestFigure8Unreliable2C 这测试用例一共运行不超过40s。前10秒在可靠网络各种插入数据。中间20秒,在不可靠网络中各种插入数据。最后10s,开始的时候,恢复网络,插入一个数据,等待这个数据被提交,如果超过这10s就报错。
这里本人补充一点:最后校验时必须所有节点(5个)全部提交(提交到applyCh
)。
这里改进了无数次调试输出信息,需要注意几组关键值,还有些方便观察的技巧:
- 注意每个节点的
rf.lastApplied
,rf.commitIndex
,len(rf.log)
- 注意所有时刻的
rf.matchIndex
和rf.nextIndex
最终发现我最后的问题是,在最后一个Term中,Leader已经对齐某节点的matchIndex
和nextIndex
后收到过时包,导致nextIndex
单独回退,使得后续不给该节点发AppendEntries了。(因为我的实现中新当选后从节点日志对齐和日志发送是两个函数,日志对齐函数理论上所有节点都对齐就结束了)所以问题还是没有在一处细节上处理好过时包。
注意,使用命令可单独测试某组样例:
go test -run TestFigur