实验说明:6.824 Lab 2: Raft
可视化:Raft
实现目标
根据论文Figure 2和5.2实现Raft系统的选主和心跳
整体流程
新节点启动时,大致的流程如下:
- 初始化自己的属性信息,主要有votedFor=-1,currentTerm=0;设置选举超时(论文的election timeout)的时间范围为1000ms-2000ms,心跳间隔为150ms;设置自己的状态为Follower,开启electionTimeout计时器。
- 如果接收到来自其他节点的投票请求,(me表示自己,request表示请求者)
- 如果me.currentTerm >request.Term,或者me.currentTerm ==request.Term而且自己已经把票投给出去了(不是投给request),此时就直接拒绝这个请求,并把自己的currentTerm放回给request
- 经过第一点的过滤后,如果判断出日志不是up-to-date的,也同样拒绝这个请求; 如果日志是up-to-date的,而且me.currentTerm <request.Term,就更新自己的currentTerm,同时设置votedFor=request.ID,并把自己的状态转为Follower,重置自己的electionTimeout计时器;
- 如果接收到来自其他节点的心跳请求(me表示自己,request表示请求者)
- 判断me.currentTerm > request.Term,就否定这个心跳请求,并返回自己的currentTerm给请求者
- 如果me.currentTerm > request.Term,就更新自己的me.currentTerm=request.Term, me.votedFor=-1;转为自己为Follower,重置自己的electionTimeout计时器;
- 如果自己的electionTimeout计时器触发了
- me.currentTerm 自增1
- 投票给自己(me.votedFor=me.ID)
- 重置自己的electionTimeout计时器
- 发送投票请求给其他节点
- 如果收到半数节点的投票,就转为Leader,并开启自己的心跳计时器,周期性给其他节点发送心跳请求
- 如果收到一个节点的否定响应,同时对端节点的currentTerm比自己的大,就设置自己的currentTerm等于对端节点的currentTerm,votedFor=-1,并转化为Follower
- 如果一直没有得到半数节点的投票,也没有收到currentTerm比自己大的否定影响,就等待electionTimeout计时器再次触发
- 作为leader给其他发送心跳请求后,如果收到拒绝的响应,同时对端节点的currentTerm比自己的大,就设置自己的currentTerm等于对端节点的currentTerm,votedFor=-1,并转化为Follower
在这个过程主要关注两个过程,代码的实现也是从这里作为入口来实现的,一个是自身程序触发的,一个是接收到来自其他节点的请求触发的;
- 自身程序触发的也就是raft.go里面的ticker函数,主要等待两个信号,一个electionTimeout计时器的触发,这里触发后就开始自我的选举;一个是心跳计时器的触发,这个触发只有身份是leader的才有动作
- 接收其他节点的请求,主要有投票请求(RequestVote)和心跳请求(AppendEntries),具体的处理逻辑看上面描述的流程就可以了
测试
root@dev-0003:~/workspace/gocode/gomodule/mit-6.824-2021/src/raft# go test -run 2A -race
Test (2A): initial election ...
... Passed -- 4.6 3 38 9716 0
Test (2A): election after network failure ...
... Passed -- 6.0 3 76 15566 0
Test (2A): multiple elections ...
... Passed -- 8.1 7 378 79306 0
PASS
ok 6.824/raft 18.757s