當server啟動后,初始狀態是follower,然后如果在集群中第一個觸發選舉超時,則變為candicate,然后向其他server發起投票,當收到過半數的贊成票后變為leader,開始每隔一個心跳時間向其他server發送心跳。啟動流程如下:
在server正常工作前,需要進行leader選舉,server啟動時為follower,在raftNode的一個routine中,會監聽ticker.C這個通道的事件,這個通道對應一個定時器,每隔100ms觸發一次,每次觸發該100ms定時事件,raftNode會向node的n.tickc通道寫入事件,這樣相當于node有了一個每隔100ms觸發一次的時鐘信號,node在自己的一個routine中會根據該時鐘信號判斷是否達到了選舉超時時間,到達選舉超時時間后,會變為candicate狀態,然后對集群中其他每個server(在peer中保存該server與其他server的關聯數據),構造選舉請求消息,追加到raft算法模塊的msgs中,而在node的一個routine中會監聽r.msgs是否有消息要處理,有的話會將r.msgs封裝成Ready寫入到node的n.readyc通道,raftNode當監聽到n.readyc通道有消息時會將消息寫入到cw.msgc通道,streamWriter會把cw.msgc通道的消息發送到對應的server。
當對應peer的streamReader監聽到p.recvc通道有事件并且事件為投票響應消息時會將消息寫入到node的n.recvc通道,node根據投票響應結果統計投贊成票的server個數,如果超過半數則變為leader,開始向其他server發送心跳。
下面看下follower在收到選舉請求時如何處理,在raft的Step方法中:
case pb.MsgVote, pb.MsgPreVote:
// The m.Term > r.Term clause is for MsgPreVote. For MsgVote m.Term should
// always equal r.Term.
if (r.Vote == None || m.Term > r.Term || r.Vote == m.From) && r.raftLog.isUpToDate(m.Index, m.LogTerm) {
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] cast %s for %x [logterm: %d, index: %d] at term %d",
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.Type, m.From, m.LogTerm, m.Index, r.Term)
r.send(pb.Message{To: m.From, Type: voteRespMsgType(m.Type)})
if m.Type == pb.MsgVote {
// Only record real votes.
r.electionElapsed = 0
r.Vote = m.From
}
} else {
r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] rejected %s from %x [logterm: %d, index: %d] at term %d",
r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.Type, m.From, m.LogTerm, m.Index, r.Term)
r.send(pb.Message{To: m.From, Type: voteRespMsgType(m.Type), Reject: true})
}
投票消息為pb.MsgVote,直接看投贊成票的條件r.raftLog.isUpToDate(m.Index, m.LogTerm),方法如下:
func (l *raftLog) isUpToDate(lasti, term uint64) bool {
return term > l.lastTerm() || (term == l.lastTerm() && lasti >= l.lastIndex())
}
m.Index為candicate的最新日志的索引位置,即參數中的lasti,m.LogTerm為candicate最新日志的任期號,即參數中的term。贊成條件為candicate的最新日志的任期號比follower的最新日志的任期號大term > l.lastTerm(),或者在雙方最新日志任期號相同的情況下,candicate最新日志的索引位置要比follower的最新日志索引位置大,即比follower的日志更新 (term == l.lastTerm() && lasti >= l.lastIndex()。
下面看下candicate對于投票響應請求的處理,在raft的stepCandicate方法中:
case myVoteRespType:
gr := r.poll(m.From, m.Type, !m.Reject)
r.logger.Infof("%x [quorum:%d] has received %d %s votes and %d vote rejections", r.id, r.quorum(), gr, m.Type, len(r.votes)-gr)
switch r.quorum() {
case gr:
if r.state == StatePreCandidate {
r.campaign(campaignElection)
} else {
r.becomeLeader()
r.bcastAppend()
}
case len(r.votes) - gr:
r.becomeFollower(r.Term, None)
}
關鍵語句在這一行gr := r.poll(m.From, m.Type, !m.Reject),下面看下這個方法:
func (r *raft) poll(id uint64, t pb.MessageType, v bool) (granted int) {
if v {
r.logger.Infof("%x received %s from %x at term %d", r.id, t, id, r.Term)
} else {
r.logger.Infof("%x received %s rejection from %x at term %d", r.id, t, id, r.Term)
}
if _, ok := r.votes[id]; !ok {
r.votes[id] = v
}
for _, vv := range r.votes {
if vv {
granted++
}
}
return granted
}
m.From為投票響應請求來源follower的id,!m.Reject是該follower是否同意投票,首先設置follower的投票結果:
r.votes[id] = v
v就是!m.Reject,然后統計所有server的贊成票數:
for _, vv := range r.votes {
if vv {
granted++
}
}
回到raft的stepCandicate方法,gr就是poll返回的贊成票數granted,當贊成票數達到r.quorum(),即過半數,如下:
func (r *raft) quorum() int {
return len(r.prs)/2 + 1
}
當贊成票達到過半數時,成為leader,并向其他follower發送附加日志rpc。