一. 问题背景
在raft协议的运行中,可能会由于网络故障、机器故障等等原因使先前的Leader挂掉。而raft有一个timeout机制,即如果follower超过timeout时间没有收到来自Leader的广播,便会成为Candidate开始选举试图成为Leader。
1.1 Leader选举
Leader选举的流程如代码所示,首先执行Candidate逻辑,决定哪个raft节点成为Candidatem,并通过随机时间机制保证每个时间点有劲仅有一个节点成为Candidate;然后,Candidate发送投票请求给各个节点,各节点根据term和Index两个信息决定是否投票;最后,Candidate收集各节点投票回复结果,投票数大于一半则成为Leader,否则变为follower。值得注意的是,Candidate在发送投票的过程中,一旦发现比自己更高的Term会立马成为follower。
func (rf *Raft) electionLoop() {
for !rf.killed() {
time.Sleep(10*time.Millisecond)
func() {
// 1. 执行Candidate逻辑
//2. 发送投票请求
//3. 统计投票结果
//4. 决策是否成为Leader
}()
}
}
二. 同步选举
2.1 同步选举
在同步选举下,如代码所示,我们会用一把锁 rf.mu.Lock() 锁住整个过程,这意味着一个raft节点走完Candiate、投票请求和统计投票结果这三个逻辑后,后续节点才能进行选举过程。如下图示例,raft1、raft2依次执行Leader 选举逻辑,上一个raft节点未结束选举,后面的raft节点不能开启选举过程。
func (rf *Raft) electionLoop() {
for !rf.killed() {
time.Sleep(10*time.Millisecond)
func() {
rf.mu.Lock()
defer rf.mu.Unlock()
// 1. 执行Candidate逻辑
for peerId := 0; peerId < len(rf.peers); peerId++ {
go func(id int) {
//2. 发送投票请求
}(peerId)
}
maxTerm := 0
for {
select {
case voteResult := <-voteResultChan:
//3. 统计投票结果
//4. 决策是否成为Leader
}()
}
}
2.2 同步选举的问题
如上图所示,在同步选举的代码逻辑下: 即当且仅当上一节点选举结束后,当前节点才开始继续进行选举过程。在此情形下,当发生如下面test代码中disconnect某个raft节点时,Candidate发送给该节点的投票信息会由于没有收到回复而一直等待,那么此节点的选举过程会一直等在统计投票阶段,等待follower的回复,由此会导致选举超时失败。
leader1 := cfg.checkOneLeader() // if the leader disconnects, a new one should be elected. cfg.disconnect(leader1) cfg.checkOneLeader() /* ......省略...... */ cfg.end() }
三. 异步选举与提前决策
3.1 异步选举
为了解决2.2同步选举的问题,我们采用异步的方式进行Leader选举,其代码核心就是:rf.mu.Unlock(),即并发1. 发送投票请求,2. 收集投票结果 并发这两个过程。如图所示,当我们并发这两个过程时,由于各个节点并发执行Leader选举过程,raft节点因网络故障而未收到回复不会影响其他节点的选举过程。通过这样异步选举的方式即可解决同步选举的问题。
func (rf *Raft) electionLoop() {
for !rf.killed() {
time.Sleep(10*time.Millisecond)
func() {
rf.mu.Lock()
defer rf.mu.Unlock()
/*执行了一段逻辑
time.Sleep(1*time.second)
*/
//-------------------------解锁:异步选举------------------------
//-------------------------未解锁:同步选举-----------------------
rf.mu.Unlock() //解锁
voteCount := 1 // 收到投票个数(先给自己投1票)
finishCount := 1 // 收到应答个数
voteResultChan := make(chan *VoteResult, len(rf.peers))
for peerId := 0; peerId < len(rf.peers); peerId++ {
go func(id int) {
if id == rf.me {
return
}
resp := RequestVoteReply{}
if ok := rf.sendRequestVote(id, &args, &resp); ok {
voteResultChan <- &VoteResult{peerId: id, resp: &resp}
} else {
voteResultChan <- &VoteResult{peerId: id, resp: nil}
}
}(peerId)
}
maxTerm := 0
for {
select {
case voteResult := <-voteResultChan:
finishCount += 1
if voteResult.resp != nil {
if voteResult.resp.VoteGranted {
voteCount += 1
}
if voteResult.resp.Term > maxTerm {
maxTerm = voteResult.resp.Term
}
/*此处为决策是否成为Leader的代码
1.------------------无提前决策-----------
2.------------------有提前决策-----------
*/
}()
}
}
3.2 提前决策
有了异步选举,各节点选举过程的开启可以互不影响。但这还不够,因为网络故障而导致收不到回复的情况仍然存在,如果Candidate必须等到收到所有投票信息才决策是否成为Leader的话,那么Candidate仍然无法决策是否成为Leader。而Leader选举的规则中,我们仅仅只需收到大于一半的赞同票即可成为Leader。这就意味着Candidate只有收到大于一半的赞同票就可以提前决策是否成为Leader,同样收到大于一半的否定票也可提前结束选举过程成为follower。
//---------------------------提前决策---------------------------
//-----当收到过半的投票时,转到该节点直接成为leader,并立马发送广播-----
if finishCount == len(rf.peers) || voteCount > len(rf.peers)/2 {
goto VOTE_END
}
}
}
VOTE_END:
rf.mu.Lock()
time.Sleep(1*time.Second) //执行了一段逻辑
// 赢得大多数选票,则成为leader
if voteCount > len(rf.peers)/2 {
rf.role = ROLE_LEADER
rf.leaderId = rf.me
rf.lastBroadcastTime = time.Unix(0, 0) //令appendEntries广播立即执行
return
}
}
四.总结
通过异步选举和提前决策,我们能完美的解决Leader选举过程中,由于网络故障而导致的选举失败问题。