ZK的leader选举过程

leader选举

在FastLeaderElection类中有一个lookForLeader()方法,开启一轮选举,无论何时只要我们的QuorumPeer状态变为looking,这个方法就会被调用,这个方法会发生notification给其它同级的服务器,并形成最终的选票(最终的leader),修改自己的状态为leader或者follower,具体步骤:

1.  创建选举对象,做选举前的初始化工作,初始化选票箱、无效选票箱,开始时间投出去多少秒之内回复最小值为200毫秒,之后每次变成上次一的二倍直到为60000固定在这个值

 try {
               self.jmxLeaderElectionBean = new LeaderElectionBean();
               MBeanRegistry.getInstance().register(
                       self.jmxLeaderElectionBean, self.jmxLocalPeerBean);
           } catch (Exception e) {
               LOG.warn("Failed to register with JMX", e);
               self.jmxLeaderElectionBean = null;
           }
           if (self.start_fle == 0) {
               self.start_fle = Time.currentElapsedTime();
           }
           try {
               // recvset  receive set用于存放来自于外部的选票,一个entry代表一次投票
               // key为投票者的serviceid ,value为选票
               //该集合相当于投票箱
               HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
   
               //outofelection out of election 退出选举
               // 其中存放的是非法选票,即投票者的状态不是looking
               HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
   
               // notTimeout notification  Timeout 开始值为200毫秒  最大超时时限为60000毫秒
               int notTimeout = finalizeWait;

   2. 将自己作为初始leader投出去,修改自己的逻辑时钟,推荐自己为leader传入 zxid、server的id和当前的逻辑时钟,然后通知给所有的同级server告诉别人自己的选择的leader

     

 synchronized(this){
                      // 逻辑时钟 这个用于改朝换代,在之前的基础上加一,如果选举成功就用这个加一之后的logicalclock 
                      logicalclock.incrementAndGet();
                      //更新当前的server的推荐信息为自己
                      // getInitId 获取当前server的id
                      //getInitLastLoggedZxid  获取当前server最后的也是最大的zxid
                      //getPeerEpoch 如果是参与选举者,获取当前逻辑时钟currentEpoch,否则是Observer 给它一个最小值0
             //updateProposal 这个方法构建推荐的leader,这里推荐的是自己
                      updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
                  }
      
                  LOG.info("New election. My id =  " + self.getId() +
                          ", proposed zxid=0x" + Long.toHexString(proposedZxid));
                  //将更新过的信息发送出去,放入队列,遍历所有具有选举权的server发送消息
                  sendNotifications();

   3. 这个while一直循环验证自己和大家的投票谁最适合做leader,直到选出最适合的然后跳出循环,通过一个接受外来通知的链式队列recvqueue获取到外来通知,然后每次取出一个Server,第一 :如果获取的值为空证明没有收到消息,这时就去判断连接管理者是否把所有的消息都投递出去了,只要有server的队列为空,就证明这个消息已经投出去有人接受到了,这时候再重新发送消息,让之前没有响应的再接受一下,如果是false证明所有的都没有收到,那是不是自己与其他的断开连接了,需要重新连接接受别人给自己通知的投票,notTimeout变成之前的两倍,如果一直其他server没有接受到则一直这么循环,最终的notTimeout一直在60000,第二如果不为空证明已经收到消息,先判断改服务和服务所选举的leader是否合法,如果合法继续执行,不合法跳出,就是服务的id在不在选举的列表中。然后进入switch (n.state)方法,如果为looking状态,进入,3.1 之后判断当前的逻辑时钟和server的逻辑时钟是否是同一个,如果是同一个判断server n推荐的leader和当前server的proposedLeader对比,对比逻辑时钟相同,zxid 和 serviceId 如果n大则返回为true,然后将n选举的leader广播出去,表示自己现在重新选择n所选择的leader作为leader,当然这时候自己的接受通知的队列又会接收到所有其他server的通知,比如之前是5 现在就是5+8,然后把n的选择放入选举结果集的map中;3.2如果server n的逻辑时钟大于当前服务的逻辑时钟,证明自己之前的选票都不算术了,清空自己的选票箱,然后判断当前的server和n所选举的server谁更适合做leader,然后更新自己的推荐的leader,广播出去;3.3 逻辑时钟小于当前server的逻辑时钟,证明这个是历史版本了,投票无效,直接丢弃,继续执行下一个。

         

 /*
                      * Loop in which we exchange notifications until we find a leader
                      */
         
                     while ((self.getPeerState() == ServerState.LOOKING) &&
                             (!stop)){
                         /*
                          * Remove next notification from queue, times out after 2 times
                          * the termination time
                          */
                         // receive queue 其中存放着所有接受到的外来的通知
                         Notification n = recvqueue.poll(notTimeout,
                                 TimeUnit.MILLISECONDS);
         
                         /*
                          * Sends more notifications if haven't received enough.
                          * Otherwise processes new notification.
                          */
                         if(n == null){
                             //manager 连接管理者,使用TCP管理,两个同辈Server的通讯并且QuorumCnxManager还管理着这些连接。haveDelivered()只要有一个其他server的队列不为空这个都返回true
                             if(manager.haveDelivered()){
                                 //重新发送,目的是为了重新接受,获取选票
                                 sendNotifications();
                             } else {
                                 //重新连接zk集群中的每一个server,
                                 // 连的时候就会接受别人的选票(别人收不齐会一直给他发)
                                 manager.connectAll();
                             }
         
                             /*
                              * Exponential backoff
                              */
                             int tmpTimeOut = notTimeout*2;
                             notTimeout = (tmpTimeOut < maxNotificationInterval?
                                     tmpTimeOut : maxNotificationInterval);
                             LOG.info("Notification time out: " + notTimeout);
                         }
                         //验证通知发送者与通知选举的leader是否合法 不能是Observer
                         else if(validVoter(n.sid) && validVoter(n.leader)) {
                             /*
                              * Only proceed if the vote comes from a replica in the
                              * voting view for a replica in the voting view.
                              */
                             switch (n.state) {
                                 case LOOKING:
                                     // If notification > current, replace and send messages out
                                     //处理当前选举过时的情况;清空票箱,更新逻辑时钟
                                     if (n.electionEpoch > logicalclock.get()) {
                                         logicalclock.set(n.electionEpoch);
                                         //清空票箱
                                         recvset.clear();
                                         //判断当前server与n谁更适合做leader:先判断时代epoch,如果时代相同判断zxid,如果相同再判断servie.id,取大的那一个
                                         // 无论谁更适合,都需要更新当前server推荐信息,然后广播出去
                                         if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                             getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
                                             updateProposal(n.leader, n.zxid, n.peerEpoch);
                                         } else {
                                             updateProposal(getInitId(),
                                                 getInitLastLoggedZxid(),
                                                 getPeerEpoch());
                                         }
                                         sendNotifications();
                                         // 处理外来n过时情况,n对于当前选举没有任何用处,直接丢掉 无效选票
                                     } else if (n.electionEpoch < logicalclock.get()) {
                                         if(LOG.isDebugEnabled()){
                                             LOG.debug("Notification election epoch is smaller than logicalclock. n.electionEpoch = 0x"
                                                 + Long.toHexString(n.electionEpoch)
                                                 + ", logicalclock=0x" + Long.toHexString(logicalclock.get()));
                                         }
                                         break;
                                         // totalOrderPredicate 用于判断外来n与当前server所推荐的leader
                                         //处理n.electionEpoch和Logicalclock相等情况
                                         // 谁最适合做新的leader
                                     } else if 
                               // 判断当前 n和推荐的proposedLeader对比,对比逻辑时钟相同,zxid 和 serviceId 如果n大则返回为true,然后将n广播出去,表示自己现在选择n作为leader,否则继续         
                                         (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                         proposedLeader, proposedZxid, proposedEpoch)) {
                                         // 更新当前server信息
                                         updateProposal(n.leader, n.zxid, n.peerEpoch);
                                         //广播出去 之后自己又会接受到别人的给反馈 之前是5在recvqueue 广播了8个出去现在变成 13个了,这个会一直增加,如果这个成立
                                         sendNotifications();
                                     }
         
         
                                     //将外来n通知为一个选票,投放到选票箱
                                     recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
         
       

   4. 判断本轮选举是否可以结束了:上面判断完逻辑时钟之后,改投票的也已经投票了,该重新选举的也重新选举了,无效的票也跳出循环了。这时候需要判断自己所推荐的leader在当前server的票箱里投的票是否已经占了一半,这个总的票数在配置文件中配置的,如果已经没有达到跳出switch继续执行,如果已经达到了,继续判断接受的队列里是否还有其它服务推荐的比这个更适合做leader的的,如果有把这个放回到队列,然后跳出这个switch,然后执行第三步,就会把选票清除,然后广播重新来,为什么会出现有的这种情况呢,打个比方,一个8个,你之前的5个已经放入投票箱了,然后第六个来了,它是因为我代码执行到这一步,别人发给我的通知,但是它推荐的leader的zxid比当前的大。如果while条件跳出 说明 n 为null  说明在剩余的通知中没有比当前server所推荐的leader 更合适,然后就清空票箱,修改自己的状态为fllower或者leader状态,等操作,最后返回投票的最终结果,返回票据

          

 //终止断言:当前Server所推荐的leader在票箱里是否过半,如果过半就结束了
            //关于过半的配置n 是你提前配置写在配置文件中的,即这个值大于n/2
             if (termPredicate(recvset,
                 new Vote(proposedLeader, proposedZxid,
                         logicalclock.get(), proposedEpoch))) {
            
                 // Verify if there is any change in the proposed leader
                 while((n = recvqueue.poll(finalizeWait,
                     TimeUnit.MILLISECONDS)) != null){
                     //对比队列里是否还有server的zxid、serverId大于当前server所推荐的leader,
                     if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                         proposedLeader, proposedZxid, proposedEpoch)){
                         //将更适合的n重新放回recqueue 以便对其进行重新投票
                         // 改循环有两个出口
                         //break: 从改出口跳出,说明在剩余的通知中找到了更适合做leader的通知 如果有,跳出循环,执行到while然后清空选票箱,通知其他的server,告诉别人自己又换选举对象了
                         //whiler 条件跳出 说明 n 为null  说明在剩余的通知中没有比当前server所推荐的leader 更合适
                         recvqueue.put(n);
                         break;
                     }
                 }
            
                 /*
              * This predicate is true once we don't read any new
              * relevant message from the reception queue
              */
                 //如果n为null 则说明当前server所推荐的leader就是最终的leader
                 if (n == null) {
                     //修改当前server的状态,非leader即following
                     self.setPeerState((proposedLeader == self.getId()) ?
                         ServerState.LEADING: learningState());
                     //形成最终选票
                     Vote endVote = new Vote(proposedLeader,
                                         proposedZxid,
                                         logicalclock.get(),
                                         proposedEpoch);
                     //清空recvqueue
                     leaveInstance(endVote);
                     return endVote;
                 }
             }
             break;

5. 无需选举的情况:这里switch 的OBSERVING这个分支不会走,这个就是多余的代码,因为最上层就判断他是不是参与者了

 

//首先要清楚两点:
       //1)当一个Server接收到其它的Server的通知后,无论自己处于什么状态,
       // 其都会向那个Server发送自己的通知。
       // 2)一个Server若能够接受到其他Server的通知,说明它不是Observer
       //而是Participant。因为sendNotification()方法不给Observer发送通知,这里其实也走不到,因为最上层就判断他是不是参与者了
       case OBSERVING:
           LOG.debug("Notification from observer: " + n.sid);
           break;
           // 1,2这两种情况都是集群已经选举出leader
        //  有两种场景会出现Leader或者follower给当前Server发送通知
       // 1)有一个新Server要加入正常运行的集群时,这个新的Server在启动时,
       // 其状态为Looking 要查找leader,其向外发通知,此时的Leader、follower的状态肯定不是looking
       // 而分别为Leading、follower状态 当 leader 、follower接收到通知后就会向其发送自己的通知,这种情况就相当于 fastPaxos
       //2)当其它Server已经在本轮选举中选出了新的leader但他没有通知到当前Server,当前的可能是网络比较慢,接受消息,别人选完了他还没好
       // 所以当前Server的状态仍保持为looking 而其它Server中的部分
       // 主机状态可能已经是leading或者following了
       //  经过分析可知,最终的两种场景是:
       //  1)当前server选举的逻辑时钟与其它follower或者leader的epoch相同
       //  2)当前server选举的逻辑时钟与其它follower或者leader的epoch不同。
       case FOLLOWING:
       case LEADING:
       /*
        * Consider all notifications from the same epoch
        * together.
        */
       if(n.electionEpoch == logicalclock.get()){
           recvset.put(n.sid, new Vote(n.leader,
                                         n.zxid,
                                         n.electionEpoch,
                                         n.peerEpoch));
          // 判断当前server选举是否应该退出本轮选举了
           // 其首先判断n所推荐的leader在当前Server的票箱中支持率是否过半
           // 若过半,再判断n所推荐的leader在outofelection中的状态是否合法,因为已经都是leader状态了所以outofelection这里肯定会有。没有就不合法,继续执行总会走到
           // 若合法则可以退出本轮选举
           if(ooePredicate(recvset, outofelection, n)) {
               //收尾工作
               self.setPeerState((n.leader == self.getId()) ?
                       ServerState.LEADING: learningState());
   
               Vote endVote = new Vote(n.leader, 
                       n.zxid, 
                       n.electionEpoch, 
                       n.peerEpoch);
               leaveInstance(endVote);
               return endVote;
           }
       }
   
       /*
        * Before joining an established ensemble, verify
        * a majority is following the same leader.
        */
       outofelection.put(n.sid, new Vote(n.version,
                                           n.leader,
                                           n.zxid,
                                           n.electionEpoch,
                                           n.peerEpoch,
                                           n.state));
   
       //若所推荐的leader在无效选票(来源就是Following和Leading)中所形成的通知集合中的支持率过半,
           // 则我就知道谁是leader,就可以退出选举了
       if(ooePredicate(outofelection, outofelection, n)) {
           synchronized(this){
               logicalclock.set(n.electionEpoch);
               self.setPeerState((n.leader == self.getId()) ?
                       ServerState.LEADING: learningState());
           }
           Vote endVote = new Vote(n.leader,
                                   n.zxid,
                                   n.electionEpoch,
                                   n.peerEpoch);
           leaveInstance(endVote);
           return endVote;
       }
       break;
   default:
       LOG.warn("Notification state unrecognized: {} (n.state), {} (n.sid)",
               n.state, n.sid);
       break;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值