实现要求

在 PartA 的基础上,加入日志。即:

  1. 在进行领导者选举时,要加入日志的比较。
  2. 领导者收到应用层发来日志后(raft.Start),要通过心跳同步给所有 Follower。
  3. 在收到多数 Follower 同步成功的请求后,Leader 要推进 CommitIndex,并让所有 Peer Apply。

写完后在 src/raft 文件夹,使用 go test -run PartB -race 来测试代码逻辑是否正确、是否有数据竞态。

实现要点

  1. 由于实现细节很多,在第一步要保证 TestBasicAgreePartB() 测试能够过。该测试用到的代码流程是,先实现 Start(),然后通过 AppendEntries RPC 发送给所有 Follower,最后在每个 Peer 上再将所有已提交日志按顺序发送到 applyCh 中。
  2. 实现领导者选举时的限制,具体可以参考论文中 5.4.1 节。
  3. 一种常见的错误现象是,已经有 Leader 当选,但还是不断的有 Peer 发起选举。可以检查下选举超时和心跳间隔的配置是否正确、候选人当选 Leader 后是否立即发起心跳、发起选举时是否检查了自己是 Leader。
  4. 你可能会用一些逻辑来频繁的检测是否满足某种条件。建议不要实现为死循环,可以插入一些 Sleep:time.Sleep(10 * time.Millisecond) 或者直接使用 Golang 的条件变量(附录1 sync.Cond)。
  5. 一定尽量有条理的组织你的代码,才能够使得出现 Bug 尽快定位,这方面我们之前章节详细讲过,可以参考:代码组织(02.Raft 代码总览) 和 PartA 中示例实现的一些组织章法。
  6. 如果你的某个测试过不了,可以参考 config.gotest_test.go 的测试逻辑,即 tester 如何制造混沌环境来对 Raft 提供的接口进行调用的。
  7. 所有关键的事件:角色变化、追加日志、应用日志等改变全局变量的各个关键环节,最好都打好日志。这样出现问题就能根据日志时间线来大致定位问题位置。
  8. 在测试的时候,可以先整体跑 go test -run PartB ,如果发现某个出现问题,再打开日志环境变量单独跑,如 VERBOSE=0 go test -run TestRPCBytesPartB | tee out.txt 以避免多个测试用例日志混在一起。

到此,你就可以自己写这部分代码然后做测试了,后面部分是实现部分,建议看到这里后,先自己实现一遍,然后跑测试。哪怕最终做不出来也先趟趟雷,才能带着问题去理解为什么要那么实现。