阅读 68

raft选举-mit6.824 lab2A

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

文章分类
后端
文章标签