任务: 实现Leader选举和心跳(即不包含日志的AppendEntries
RPC). Part 2A的目标是让一个Leader能被选举出来, 并在没有宕机的情况下保持Leader身份, 在这个Leader宕机或这个Leader发送/接收的包丢失时让一个新的Leader接替它. 运行go test -run 2A
来测试你2A部分的代码.
提示:
- 在
raft.go
文件的Raft
结构体中添加你需要的任何状态. 你还需要定义一个结构体来保存每条日志的信息. 你的代码需要尽可能按照论文的图2来实现. - Go RPC只会发送结构体中大写字母开头的字段. 子结构体同样也需要有首字母大写的字段名(例如表示日志记录数组的字段). 这些实验中最常出现的Bug就是来自忘记将字段名首字母大写.
- 补充
RequestVoteArgs
和RequestVoteReply
结构体. 修改Make()
来创建一个后台Go线程, 用来在一段时间内没有接收到其他节点的消息时, 发送RequestVote
RPC来周期性地开始一轮选举. 这样一个节点才能知道谁是Leader(当存在一个Leader的时候), 或让自己成为Leader. 实现处理RequestVote()
RPC的方法, 以便服务器能互相投票. - 要实现心跳消息, 需要定义一个
AppendEntries
RPC结构体(虽然可能还不需要所有的参数), 并让Leader周期性地发送. 写一个处理AppendEntries
RPC的函数, 这个函数需要重置选举超时时间以便当已经选出Leader后, 其他服务器不会进入Leader角色. - 确保不同的节点上选举超时不会同时发生, 否则所有的服务器都会为它们自己投票导致没有人能真正被选举出来.
- 测试程序要求Leader每秒钟发送不超过10次的心跳RPC.
- 测试程序要求你的Raft在旧的Leader宕机5秒之内选出一个新Leader(节点中的大多数仍可通信的情况下). 注意在选票被平分的情况下, Leader选举可能需要进行多轮(当网络包丢失, 或Candidate不幸地随机到了同样的超时时间时). 你需要选择足够短的选举超时时间(以及心跳间隔), 以便选举能够在5秒内完成, 即便需要多轮选举.
- 论文的5.2节提到选举超时时间在150到300毫秒. 这个范围只在Leader每150毫秒发送一个心跳包的情况下才有意义. 因为测试程序限制你每秒只能发送10个心跳包, 因此你需要选择一个大于论文中的150到300毫秒的选举超时时间, 但不能太大, 因为太大了可能没法在5秒内选出一个Leader.
- 你可能会用到Go提供的
time.Sleep()
和rand
. - 你写的代码需要周期性地或是在一定延时后执行, 最简单的实现方式是写一个循环调用
time.Sleep()
的Go线程. - 如果你的代码无法通过测试, 请重读论文的图2; Leader选举完整的逻辑已经写在这个图中的各个部分了.
- 调试你代码的一个好办法是在一个节点发送或接收消息时插入输出语句, 并用
go test -run 2A > out
把输出都导出到一个文件中. 然后, 通过观察out
文件中的消息, 你才能判断你的各处实现是否与预期效果一致. 你会发现util.go
中的DPrintf
方法在调试不同的问题时能用来调整输出. - 你需要用
go test -race
来检查你的代码, 并修复它报告的任何竞态条件.