概述
Lab2B需要我们完善rf.Start(..)
这个函数.这个函数接受一条command
作为入参,如果当前节点rf
是leader
, 那么就会开始达成共识的过程.
如果当前节点是leader,rf.Start()
会把命令压入自身日志,立刻返回index。调用者拿到了一个index,这并不意味着任务完成了。因为raft只保证committed的日志会被执行,所以调用者需要继续监听applyChan
。只有applyChan
中出来了一条index相同,内容也相同的日志,此时调用方才能执行命令。
如果出来了一条index相同但是内容不同的日志,表示之前提交的那条日志被覆盖掉了,需要调用方告知客户端重试。
测试
有如下测试场景:
- basic agreement, 没有出错的情况下达成一致。
- agreement despite follower disconnection ...
- no agreement if too many followers disconnect ...
- concurrent Start()s ...
- rejoin of partitioned leader ...
- leader backs up quickly over incorrect follower
- RPC counts aren't too high ,rpc间隔大概为100ms即可
重构代码
笔者原来的程序通过向channel发送信号来进行状态变更、重置计时器,代码冗长凌乱,难以调试。于是笔者花时间重写了代码,总体而言是大量减少goroutine,select和channel的使用,改为同步调用。
新程序大体如下:
- 用状态机模式实现:raft节点运行一个无限循环,在循环内跟着自身状态进入
leader()
,candidate()
,follower()
三个处理函数,每一个都有不同的行为,在恰当时候检查自身状态是否变化。 - 发送HB的模式改为leader周期性创建n-1个goroutine,一个goroutine负责发送一个心跳。
- 定时器用一个成员
Timer
,而不是使用一个单独的、常驻的goroutine负责计时,这样可以在普通函数中用Timer.Stop(), Timer.Reset(..)
来控制计时. - 用原子类型,减少锁的使用。
- 认真阅读论文,按论文要求处理细节。
- 对于论文没有提到的细节,参考student's guide的实现,例如如何处理过时rpc,何时重置计时器。
重构后的代码减少了200+行,而且顺利地通过了Lab2A,Lab2B(连续100次通过)。尽量减少goroutine,select和chan的使用,方便其他语言参考。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。