EtcdRaft源码分析(选举投票)

EtcdRaft源码分析(选举超时)的篇章里面讲到,当超时的时候,Leader外的成员会造反发起选举。我们接下来看下Raft里面选民是怎么投票的。

投票

Step

case m.Term > r.Term:
   if m.Type == pb.MsgVote || m.Type == pb.MsgPreVote {
      force := bytes.Equal(m.Context, []byte(campaignTransfer))
      inLease := r.checkQuorum && r.lead != None && r.electionElapsed < r.electionTimeout
      if !force && inLease {
         // If a server receives a RequestVote request within the minimum election timeout
         // of hearing from a current leader, it does not update its term or grant its vote
         r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] ignored %s from %x [logterm: %d, index: %d] at term %d: lease is not expired (remaining ticks: %d)",
            r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.Type, m.From, m.LogTerm, m.Index, r.Term, r.electionTimeout-r.electionElapsed)
         return nil
      }
   }
   switch {
   case m.Type == pb.MsgPreVote:
      // Never change our term in response to a PreVote
   case m.Type == pb.MsgPreVoteResp && !m.Reject:
      // We send pre-vote requests with a term in our future. If the
      // pre-vote is granted, we will increment our term when we get a
      // quorum. If it is not, the term comes from the node that
      // rejected our vote so we should become a follower at the new
      // term.
   default:
      r.logger.Infof("%x [term: %d] received a %s message with higher term from %x [term: %d]",
         r.id, r.Term, m.Type, m.From, m.Term)
      if m.Type == pb.MsgApp || m.Type == pb.MsgHeartbeat || m.Type == pb.MsgSnap {
         r.becomeFollower(m.Term, m.From)
      } else {
         r.becomeFollower(m.Term, None)
      }
   }
  • 如果候选人的任期比自己高,如果开启了checkQuorum就说明Leader在每个心跳的周期都会去检查成员的活跃度,而且现在Leader还在,且现在还没有选举超时。
    • 这里我理解,首先checkQuorum如果探测到网络不健康,Leader会被撸掉,现在还存在说明网络是健康的。其次,还没到选举的时候,这次选举我严重怀疑正当性。所以忽略。
  • 这里有个点要提下,如果任期比我高,并且类型是同步日志或快照或心跳的消息,那么当前不管什么身份,全部要臣服,认对方为Leader。说明这三种类型只有可能是Leader发出。
    • 其他类型可以不认对方,但是全部贬为庶民Follower。
    • MsgPreVote,MsgPreVoteResp,和消息被拒绝这几种情况,可以不处理。这个待分析
  • 所以在Raft里面,对方的任期比自己高,是很严重的事情。必须要立即行动。不然形势会越来越严峻。
case m.Term < r.Term:
  ...
   } else if m.Type == pb.MsgPreVote {
      // Before Pre-Vote enable, there may have candidate with higher term,
      // but less log. After update to Pre-Vote, the cluster may deadlock if
      // we drop messages with a lower term.
      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, Term: r.Term, Type: pb.MsgPreVoteResp, Reject: true})
   } 
   ...
}
  • 这里得提下,如果是预选举的情况,如果对方还没有自己的任期高,那么你资格还没我老,我当然要拒绝你啊。给他投反对票。
case pb.MsgVote, pb.MsgPreVote:
   if r.isLearner {
      // TODO: learner may need to vote, in case of node down when confchange.
      r.logger.Infof("%x [logterm: %d, index: %d, vote: %x] ignored %s from %x [logterm: %d, index: %d] at term %d: learner can not vote",
         r.id, r.raftLog.lastTerm(), r.raftLog.lastIndex(), r.Vote, m.Type, m.From, m.LogTerm, m.Index, r.Term)
      return nil
   }
   // We can vote if this is a repeat of a vote we've already cast...
   canVote := r.Vote == m.From ||
      // ...we haven't voted and we don't think there's a leader yet in this term...
      (r.Vote == None && r.lead == None) ||
      // ...or this is a PreVote for a future term...
      (m.Type == pb.MsgPreVote && m.Term > r.Term)
   // ...and we believe the candidate is up to date.
   if canVote && 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, Term: m.Term, 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, Term: r.Term, Type: voteRespMsgType(m.Type), Reject: true})
   }
  • 如果是learner,那么没有资格投票,你先赶上进度再说把。

  • 验证是否能投票

    • 首先如果已经给对方投过票了,不介意再投
    • 如果还没有投票,且现在还没有选出Leader,当然要投
    • 如果是准选举,对方的任期比自己高,可以考虑投票
  • 如果可以投票,这里分两种情况,PreElection和Election

    • 如果是Pre,那么它发起选举的时候会把当前term+1,那么这个方法会返回true的话,说明自己的任期真的比对方低。
    • 如果不是Pre,那么它发起选举的时候就是当前term,那么这个方法返回true,说明对方比自己只多不少。
    • 给对方发消息,MsgVoteResp或MsgPreVoteResp
    • 如果是正式选举的话,选举计时开始,记下来我给他投了票
func (l *raftLog) isUpToDate(lasti, term uint64) bool {
  return term > l.lastTerm() || (term == l.lastTerm() && lasti >= l.lastIndex())
}
  • 如果不能投票,给对方投反对票,Reject: true

Candidate&PreCandidate

候选人收到各个成员发来的投票,

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:
      // pb.MsgPreVoteResp contains future term of pre-candidate
      // m.Term > r.Term; reuse r.Term
      r.becomeFollower(r.Term, None)
   }
  • 统计现在收到的同意票,计下票数
  • 如果超过半数
    • 如果之前是准选举,那么这次开始玩真的
    • 如果之前是正式选举,那么恭喜你,你成功当选
  • 如果反对票超过半数,那么对不起,你落选了,直接变为庶民。但是现在还不知道leader是谁,那么先空着。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容