etcd-raft源碼分析3-leader選舉

當server啟動后,初始狀態是follower,然后如果在集群中第一個觸發選舉超時,則變為candicate,然后向其他server發起投票,當收到過半數的贊成票后變為leader,開始每隔一個心跳時間向其他server發送心跳。啟動流程如下:

leader選舉.png

在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。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容