raft是分布式系统中的一致性复制算法,保证强一致性,根据paxos演变而来,为了能更好的生产落地,便于理解。lab2A主要是关于raft的选举过程的代码实现,接下来是自己理解的点。
先大概聊一下自己理解的raft算法中关于选举部分的东东。一个集群中的每个server都有三种状态,分别是Follower,Candidate和Leader。最开始的情况下是Follower,每台机器都设置一个随机的定时时间(为了避免同一时间多台机器同时选举),当时间到了,Follower变成Candidate开始选举,Candidate为自己投票并发送投票请求给其他peers,如果返回的Term比自己大,则自己转变成Follower,如果返回的Term比自己小且赞成票,则Candidate转换成Leader,开始发送心跳(LogEntries为空则为心跳),如果收到的Term比自己大,则专程Follower。对于接收者来说,收到投票请求和心跳后根据自身的term和state进行判断是否要投赞成票即可(细节看后续)。
首先是构建大的框架,设置开始选举和发送心跳的chan, 后台起协程跑开始选举定时器和发送心跳定时器,当时间到了之后触发开始选举和发送心跳。在这里的定时器可以采用for循环加time.Sleep的方式,也可以通过ticker的方式。同时,为了能在合适的时机(后面会说)重新开始计时,需要有resetTimer的方法来控制什么时候重置定时器。具体来说是如果当前状态为Candidate, 且判断当前时间减去lastTimeout时间,如果大于设定的超时时间(随机数),则开始选举;如果当前状态为Follower,且超时,则变成Candidate,并开始选举;如果当前状态为Leader,且发送心跳时间到了,则发送心跳。
func (rf *Raft) mainLoop() {
for{
select {
case <- rf.timerElectionChan:
go rf.startElection()
case <- rf.timerHeartbeatChan:
go rf.broadcastHeartbeat()
}
}
}
func (rf *Raft) timerElection() {
for {
rf.mu.Lock()
if rf.state != StateLeader {
timeElapsed := (time.Now().UnixNano() - rf.lastResetElectionTimer) / 1e6
if timeElapsed > rf.timeoutElection {
rf.timerElectionChan <- true
}
}
rf.mu.Unlock()
time.Sleep(time.Millisecond * 10)
}
}
func (rf *Raft) timerHeartbeat() {
for {
rf.mu.Lock()
if rf.state == StateLeader {
timeElapsed := (time.Now().UnixNano() - rf.lastResetHeartbeatTimer) / 1e6
if timeElapsed > rf.timeoutHeartbeat {
rf.timerHeartbeatChan <- true
}
}
rf.mu.Unlock()
time.Sleep(time.Millisecond * 10)
}
}
func (rf *Raft) resetTimerElection() {
rand.Seed(time.Now().UnixNano())
rf.timeoutElection = rf.timeoutHeartbeat * 4 + rand.Int63n(150)
rf.lastResetElectionTimer = time.Now().UnixNano()
}
func (rf *Raft) resetTimerHeartbeat() {
rand.Seed(time.Now().UnixNano())
rf.timeoutHeartbeat = 100
rf.lastResetHeartbeatTimer = time.Now().UnixNano()
}
接着是开始选举部分。转变成Candidate,为自己投票,遍历向所有peers发送投票请求,如果收到赞成票,票数增加,判断当到达了大多数投票后,专程leader,发送心跳。
func (rf *Raft) startElection() {
rf.mu.Lock()
rf.convertTo(StateCandidate)
rf.mu.Unlock()
// 发送请求为自己投票的请求
votedNum := 1 //为自己投票
var wg sync.WaitGroup
wg.Add(len(rf.peers))
var lastLogIndex int
var lastLogTerm int
if len(rf.log) == 0{
lastLogIndex = -1
lastLogTerm = 0
}else{
lastLogIndex = len(rf.log) - 1
lastLogTerm = rf.log[len(rf.log)-1].Term
}
reqVoteArgs := &RequestVoteArgs{
Term: rf.currentTerm,
CandidateID: rf.me,
LastLogIndex: lastLogIndex,
LastLogTerm: lastLogTerm,
}
replyCh := make(chan *RequestVoteReply, len(rf.peers))
for idx, _ := range rf.peers {
curIdx := idx
go func(idx int) {
rf.mu.Lock()
if idx == rf.me || !(rf.state == StateCandidate){
wg.Done()
rf.mu.Unlock()
return
}else{
rf.mu.Unlock()
}
rspVoteArgs := &RequestVoteReply{}
rf.sendRequestVote(idx,reqVoteArgs,rspVoteArgs)
replyCh <- rspVoteArgs
wg.Done()
}(curIdx)
}
//如果majority都同意了,成为leader
go func() {
wg.Wait()
close(replyCh)
}()
for reply := range replyCh{
rf.mu.Lock()
if reply.Term > rf.currentTerm{
rf.currentTerm = reply.Term
//转换成follower
rf.convertTo(StateFollower)
}
if reply.VoteGranted {
votedNum++
}
rf.mu.Unlock()
if votedNum > len(rf.peers) / 2{
//只要到大多数票数就可以往下走了,剩下的返回不用在意了
//如果等待所有都返回了再行动,可能其他都server等不及就自己发起选举了
break
}
}
if votedNum >= len(rf.peers) / 2 + 1{
rf.mu.Lock()
rf.convertTo(StateLeader)
rf.mu.Unlock()
rf.broadcastHeartbeat()
}
}
而接收到心跳的server执行逻辑如下,如果收到的Term大于本身,则变成follower,如果在这个任期没有给人投过票或者,投票的人就是发起请求的server,则投赞成票并重置选举定时器;如果收到的term小于本身,则投反对票。
func (rf *Raft) RequestVote(args *RequestVoteArgs, reply *RequestVoteReply) {
// Your code here (2A, 2B).
rf.mu.Lock()
defer rf.mu.Unlock()
if args.Term >= rf.currentTerm{
rf.convertTo(StateFollower)
rf.currentTerm = args.Term
}
if args.Term < rf.currentTerm || (rf.votedFor != -1 && rf.votedFor != args.CandidateID){
reply.Term = rf.currentTerm
reply.VoteGranted = false
return
}
if rf.votedFor == -1 || rf.votedFor == args.CandidateID{
reply.Term = rf.currentTerm
reply.VoteGranted = true
rf.votedFor = args.CandidateID
rf.resetTimerElection()
}
}
发送心跳,保证依旧是leader,重置心跳定时器,给所有的peers发送心跳,如果没有成功,则转换成Follower.
func (rf *Raft) broadcastHeartbeat() {
rf.mu.Lock()
if rf.state != StateLeader{
rf.mu.Unlock()
return
}
rf.resetTimerHeartbeat()
rf.mu.Unlock()
for i := 0; i < len(rf.peers); i++{
if i == rf.me{
continue
}
go func(id int) {
rf.mu.Lock()
args := AppendEntriesArgs{
Term:rf.currentTerm,
}
rf.mu.Unlock()
var reply AppendEntriesReply
if rf.sendAppendEntries(id,&args,&reply){
rf.mu.Lock()
defer rf.mu.Unlock()
if rf.state != StateLeader{
return
}
if rf.currentTerm != args.Term{
return
}
if reply.Success{
continue
}else{
if reply.Term > rf.currentTerm{
rf.convertTo(StateFollower)
rf.currentTerm = reply.Term
return
}
}
}
}(i)
}
}
接收心跳
func (rf *Raft) AppendEntries(args *AppendEntriesArgs, reply *AppendEntriesReply) {
rf.mu.Lock()
defer rf.mu.Unlock()
if args.Term < rf.currentTerm{
reply.Term = rf.currentTerm
reply.Success = false
return
}
rf.resetTimerElection()
if args.Term > rf.currentTerm || rf.state != StateFollower{
rf.convertTo(StateFollower)
rf.currentTerm = args.Term
}
reply.Term = rf.currentTerm
reply.Success = true
}
总结:
1、 所以重置选举定时器的场景:最开始Make的时候,投出赞成票的时候,变成Candidate的时候,收到心跳的时候 重置心跳定时器的场景:最开始Make的时候,成为leader广播发送心跳的时候
2、锁的使用需要当心,在发送请求的时候不要加锁,否则容易造成死锁
3、判断在一段时间内follower没有收到任何心跳,则自己成为candidate发起选举的实现,并不是通过设置一个flag来看是否收到心跳,而是采用重置选举定时器来实现
参考: www.ulunwen.com/archives/22… nil.csail.mit.edu/6.824/2020/… zhuanlan.zhihu.com/p/152236946