终于来到了最难的lab3,我做的是2024 版,新增了一个KVServer为lab2,原来的raft就变成lab3了
首先阅读论文:
Raft是一种基于日志复制的一致性算法,比paxos更加简单易学且易于实现
Raft技术特点:
- 将问题分解为独立的易于理解和解决的子问题
- 通过减少需要考虑的状态来简化状态空间。这使得系统更加一致并尽可能消除不确定的状态
具体的论文细节就不赘述了,网上有很多写的很好的总结,本文主要记录我的个人实现过程和细节
lab3A是实现leader的选举
- 首先想着把论文中Figure2中的state写进去,这一步简单地按照论文中的Figure2中写就行了
- 用自带的debug函数Dprintf函数探了探路,启动go test -run 3A后发现创建了3个raft。这下目标明确了:**为Raft添加状态,leader、follower、candidate。**make函数创建raft时当然是follower状态啦
- 这个时候就没什么思路了,到处看了看发现getstate这个函数比较好写,就写了
- 论文中提到了选取Leader时出现多个Leader的情况,设置一个随机的选取时间避免选取出多个Leader的情况 random
- 重新看了看论文的实现思路并参考了大量文章,决定使用Timer定时器和ticker循环定时器分别作为选举和心跳的定时器
- 实现ticker(),框架中给了for rf.killed() == false{},使用select case 调用心跳或者选举
- 重点实现函数electLeader,electTime到期时调用,Leader变成candidate,currentTerm++,给自己投票(voteFor=me)然后遍历rf.peers请求所有peer(类型为Candidate)为其投票,其中记录投票的票数。如果过半则当选Leader,当选后立刻给其他peer广播心跳heartBeat()
注意:若其他peer的Term大于currentTerm则本rf退化为Follwer,currentTerm也更新为该Term - 再实现心跳函数heartBeat,心跳函数用time.ticker调用,循环定时器完美满足此应用场景,只需要在心跳函数中增加一个是否为Leader的判断就行
细节
多多注意锁的使用,本人就是在锁的使用上犯了糊涂,妄图使用一把大锁来包平安,但是在并发的条件下似乎不太行得通,必须减少锁的粒度(也就是被锁住的操作)
另外还要注意锁的lock与unlock的搭配,上锁后必须解锁!!
debug
- 多利用raft/util.go中的DPrintf函数进行分析
- 在实现的过程中出现了warning: term changed even though there were no failures的问题,经过大量查阅资料和DPrintf来检查,先后发现了三个错误,一是重置rf的electTime的时机,二是原来心跳的定时器使用的是Timer单次定时器而不是Ticker循环定时器,三是在调用heartBeat时对它加上了锁,在heartBeat内部也加了锁,导致卡在heartBeat函数内部
- 有四个重置electTime的时机
- 给别的peer投票
- 收到了符合要求的heartbeat
- 自己发起了选举
- 自己是Leader,给别的Follower发送heartBeat。遍历到自己时,不需要给自己发送heartbeat,但要记得重置electTime
- 改用循环定时器Ticker来触发正常的心跳以便维持一个未出错的Leader任期(Term)
- 调用heartBeat时不需要加锁