Zookeeper のソース コード分析 - リーダー選挙 (FastLeaderElection 戦略)

復習:
前回の記事では、入り口を見つけ、同時に zk 選挙で使用されるアルゴリズム戦略、つまり FastLeaderElection 戦略を見つけました。この記事では、FastLeaderElection アルゴリズムが選挙を開始する方法について詳しく説明します
ディディ

以下のFastLeaderElection は
、870 行目に添付されている lookForLeader() メソッドから始まる zk 選出アルゴリズムのコアです。自分用のメモをいくつか追加しました

public Vote lookForLeader() throws InterruptedException {
    
    
        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 {
    
    

            //收到的投票
            HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
            //投票结果
            HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();

            int notTimeout = finalizeWait;

            synchronized(this){
    
    
                logicalclock.incrementAndGet();//原子long类型,增加逻辑时钟,就是epoch
                //更新选举提议,myid  zxid  epoch
                updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
            }

            LOG.info("New election. My id =  " + self.getId() +
                    ", proposed zxid=0x" + Long.toHexString(proposedZxid));
            //发送给所有的节点
            sendNotifications();

            /*
             * Loop in which we exchange notifications until we find a leader
             */
            //如果是looking状态,我们会一直去和其他节点交互信息,直到选举出leader
            while ((self.getPeerState() == ServerState.LOOKING) &&
                    (!stop)){
    
    
                /*
                 * Remove next notification from queue, times out after 2 times
                 * the termination time
                 */
                //从接收队列中拿到投票信息
                Notification n = recvqueue.poll(notTimeout,
                        TimeUnit.MILLISECONDS);

                /*
                 * Sends more notifications if haven't received enough.
                 * Otherwise processes new notification.
                 */
                if(n == null){
    
    
                    if(manager.haveDelivered()){
    
      //检查所有的队列是否为空
                        sendNotifications();        //如果为空发送通知
                    } else {
    
    
                        manager.connectAll();  //如果没有投递出去,可能是其他server还没有启动,尝试连接
                    }

                    /*
                     * Exponential backoff
                     */
                    int tmpTimeOut = notTimeout*2;
                    notTimeout = (tmpTimeOut < maxNotificationInterval?
                            tmpTimeOut : maxNotificationInterval);
                    LOG.info("Notification time out: " + notTimeout);
                }
                //判断收到的投票的sid,
                //这里判断的是收到的sid是不是属于当前集群内的
                else if (validVoter(n.sid) && validVoter(n.leader)) {
    
    
                    /*
                     * Only proceed if the vote comes from a replica in the current or next
                     * voting view for a replica in the current or next voting view.
                     */
                    switch (n.state) {
    
     //判断当前节点状态
                    case LOOKING:
                        // If notification > current, replace and send messages out
                        //收到的epoch是不是比当前选举的epoch要大,如果大那么代表是新一轮选举
                        if (n.electionEpoch > logicalclock.get()) {
    
    
                            logicalclock.set(n.electionEpoch);  //更新当前epoch
                            recvset.clear();  //情况收到的投票
                            //进行投票

                            /*
                             * We return true if one of the following three cases hold:
                             * 1- New epoch is higher
                             * 收到的epoch大于当前的epoch 胜出选举
                             * 2- New epoch is the same as current epoch, but new zxid is higher
                             * 如果收到的epoch等于当前epoch,那么收到的zxid大于当前zxid胜出选举
                             * 3- New epoch is the same as current epoch, new zxid is the same
                             *  as current zxid, but server id is higher.
                             * 如果收到的epoch等于当前epoch,zxid登录当前zxid,
                             * 那么收到的myid大于当前myid的胜出选举
                             */
                            if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                    getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
    
    
                                updateProposal(n.leader, n.zxid, n.peerEpoch); //把胜出的消息更新到投票提议中
                            } else {
    
      //如果收到消息没有胜出,那么选择当前的消息更新到投票提议中
                                updateProposal(getInitId(),
                                        getInitLastLoggedZxid(),
                                        getPeerEpoch());
                            }
                            sendNotifications();  //发送投票消息
                        } 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;
                            //如果收到的逻辑时钟相等,则去对比myid 、zxid、epoch
                        } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                proposedLeader, proposedZxid, proposedEpoch)) {
    
    
                            updateProposal(n.leader, n.zxid, n.peerEpoch);
                            sendNotifications();
                        }

                        if(LOG.isDebugEnabled()){
    
    
                            LOG.debug("Adding vote: from=" + n.sid +
                                    ", proposed leader=" + n.leader +
                                    ", proposed zxid=0x" + Long.toHexString(n.zxid) +
                                    ", proposed election epoch=0x" + Long.toHexString(n.electionEpoch));
                        }

                        //把投票结果存到本地,用来做最终判断
                        recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

                        //判断选举是否结束,默认算法过半同意
                        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){
    
    
                                if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                        proposedLeader, proposedZxid, proposedEpoch)){
    
    
                                    recvqueue.put(n);//获得最新的记过
                                    break;
                                }
                            }

                            /*
                             * This predicate is true once we don't read any new
                             * relevant message from the reception queue
                             */
                            if (n == null) {
    
    
                                self.setPeerState((proposedLeader == self.getId()) ?
                                        ServerState.LEADING: learningState());

                                Vote endVote = new Vote(proposedLeader,
                                        proposedZxid, proposedEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }
                        break;
                    case OBSERVING:  //如果是
                        LOG.debug("Notification from observer: " + n.sid);
                        break;
                    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));
                            if(termPredicate(recvset, new Vote(n.leader,
                                            n.zxid, n.electionEpoch, n.peerEpoch, n.state))
                                            && checkLeader(outofelection, n.leader, n.electionEpoch)) {
    
    
                                self.setPeerState((n.leader == self.getId()) ?
                                        ServerState.LEADING: learningState());

                                Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }

                        /*
                         * Before joining an established ensemble, verify that
                         * a majority are following the same leader.
                         * Only peer epoch is used to check that the votes come
                         * from the same ensemble. This is because there is at
                         * least one corner case in which the ensemble can be
                         * created with inconsistent zxid and election epoch
                         * info. However, given that only one ensemble can be
                         * running at a single point in time and that each 
                         * epoch is used only once, using only the epoch to 
                         * compare the votes is sufficient.
                         * 
                         * @see https://issues.apache.org/jira/browse/ZOOKEEPER-1732
                         */
                        outofelection.put(n.sid, new Vote(n.leader, 
                                IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state));
                        if (termPredicate(outofelection, new Vote(n.leader,
                                IGNOREVALUE, IGNOREVALUE, n.peerEpoch, n.state))
                                && checkLeader(outofelection, n.leader, IGNOREVALUE)) {
    
    
                            synchronized(this){
    
    
                                logicalclock.set(n.electionEpoch);
                                self.setPeerState((n.leader == self.getId()) ?
                                        ServerState.LEADING: learningState());
                            }
                            Vote endVote = new Vote(n.leader, n.zxid, n.peerEpoch);
                            leaveInstance(endVote);
                            return endVote;
                        }
                        break;
                    default:
                        LOG.warn("Notification state unrecoginized: " + n.state
                              + " (n.state), " + n.sid + " (n.sid)");
                        break;
                    }
                } else {
    
    
                    if (!validVoter(n.leader)) {
    
    
                        LOG.warn("Ignoring notification for non-cluster member sid {} from sid {}", n.leader, n.sid);
                    }
                    if (!validVoter(n.sid)) {
    
    
                        LOG.warn("Ignoring notification for sid {} from non-quorum member sid {}", n.leader, n.sid);
                    }
                }
            }
            return null;
        } finally {
    
    
            try {
    
    
                if(self.jmxLeaderElectionBean != null){
    
    
                    MBeanRegistry.getInstance().unregister(
                            self.jmxLeaderElectionBean);
                }
            } catch (Exception e) {
    
    
                LOG.warn("Failed to unregister with JMX", e);
            }
            self.jmxLeaderElectionBean = null;
            LOG.debug("Number of connection processing threads: {}",
                    manager.getConnectionThreadCount());
        }
    }

上記のコードでは、2 つの HashMap が定義されていることがわかり、それらをコメントアウトしました。
//受け取った投票
HashMap<Long, Vote> recvset = new HashMap<Long, Vote>();
//投票結果
HashMap<Long, Vote> outofelection = new HashMap<Long, Vote>();
これら 2 つの HashMap 1 つは、 1 つは現在のノードが受け取った投票で、もう 1 つは現在のノードの投票の結果です。
まずこれら 2 つの変数を覚えてから、振り返ります。

892行目あたり

synchronized(this){
     
     
   logicalclock.incrementAndGet();//原子long类型,增加逻辑时钟,就是epoch
  //更新选举提议,myid  zxid  epoch
   updateProposal(getInitId(), getInitLastLoggedZxid(), getPeerEpoch());
}

updateProposal メソッドは投票提案を更新するもので、3 つのパラメーターを渡します。これらの 3 つのパラメーターが何であるかを見てみましょう。

1、getInitId()

private long getInitId(){
     
     
       if(self.getQuorumVerifier().getVotingMembers().containsKey(self.getId()))       
           return self.getId();
      else return Long.MIN_VALUE;
   }

self.getId() で myid を取得します。myid をどこかで見たことがありますか? そうです、これは、zk クラスターを構築したときに構成した myid ファイル内の値です。

2、getInitLastLoggedZxid()

private long getInitLastLoggedZxid(){
     
     
       if(self.getLearnerType() == LearnerType.PARTICIPANT)
           return self.getLastLoggedZxid();
       else return Long.MIN_VALUE;
   }

現在のホスト ノードで認識される最大の zxid は次のとおりです。zxid とは何ですか? ノードの最終トランザクション ID です

3、getPeerEpoch()

 private long getPeerEpoch(){
     
     
    if(self.getLearnerType() == LearnerType.PARTICIPANT)
    	try {
     
     
    		return self.getCurrentEpoch();
    	} catch(IOException e) {
     
     
    		RuntimeException re = new RuntimeException(e.getMessage());
   		re.setStackTrace(e.getStackTrace());
   		throw re;
  	}
    else return Long.MIN_VALUE;
  }

ここでエポックとは論理クロックについて説明されており、エポックは選挙の各ラウンド後に自動的に増加します。これは現在の論理クロックです

updateProposal メソッドの 3 つのパラメーターはすべて、それらが何であるかを知っています。次に、updateProposal メソッドが何を行うかを見てみましょう。

synchronized void updateProposal(long leader, long zxid, long epoch){
     
     
      if(LOG.isDebugEnabled()){
     
     
           LOG.debug("Updating proposal: " + leader + " (newleader), 0x"
                   + Long.toHexString(zxid) + " (newzxid), " + proposedLeader
                   + " (oldleader), 0x" + Long.toHexString(proposedZxid) + " (oldzxid)");
      }
      proposedLeader = leader;
       proposedZxid = zxid;
       proposedEpoch = epoch;
   }

この方法は非常に単純です。myid、zxid、およびエポックをそれぞれ、projectedLeader、projectedZxid、およびprojectedEpochの3つの変数に割り当てることです。具体的な選出アルゴリズムはまだ見ていません。心配しないで、裏面を見てください
。 updateProposal メソッドが実行された後、もう一度何をしましたか?

SendNotifications()行 901 付近
; 文字通り、通知を送信するためのメソッドがあります。クリックして見てみましょう。

private void sendNotifications() {
     
     
     for (long sid : self.getCurrentAndNextConfigVoters()) {
     
     
         QuorumVerifier qv = self.getQuorumVerifier();
         ToSend notmsg = new ToSend(ToSend.mType.notification,
                 proposedLeader,
                 proposedZxid,
                logicalclock.get(),
                QuorumPeer.ServerState.LOOKING,
                sid,
               proposedEpoch, qv.toString().getBytes());
       if(LOG.isDebugEnabled()){
     
     
            LOG.debug("Sending Notification: " + proposedLeader + " (n.leader), 0x"  +
                 Long.toHexString(proposedZxid) + " (n.zxid), 0x" + Long.toHexString(logicalclock.get())  +
                  " (n.round), " + sid + " (recipient), " + self.getId() +
                   " (myid), 0x" + Long.toHexString(proposedEpoch) + " (n.peerEpoch)");
         }
         sendqueue.offer(notmsg);
     }
   }

ここでは、projectedLeader、projectedZxid、projectedEpoch を ToSend にカプセル化し、ToSend を LinkedBlockingQueue キュー (sendqueue) に入れます。
sendNotifications() メソッドは、projectedLeader、projectedZxid、projectedEpoch をメッセージにカプセル化し、そのメッセージをキューに入れることになっていることがわかります。具体的には、zk がこのキュー内のメッセージを取得するとき、最初にそれを無視しましょう。そしてロジックに従います。
次に、sendNotifications() メソッドを見てください。

907 行目の近くにループがあります。ループに入って
914 行目あたりを見てみましょう

//从接收队列中拿到投票信息
 Notification n = recvqueue.poll(notTimeout,TimeUnit.MILLISECONDS);

recvqueue これは受信キューの 1 つで、投票情報が受信キューから取り出され、通知に渡されます。

938行目あたり

else if (validVoter(n.sid) && validVoter(n.leader)) {
     
     

ここでは、sid が現在のクラスターの下にあるかどうかを判断します。sid は送信者のアドレスです。
後で別の判断があります。switch (n.state) これは、送信者のノードのステータスを判断するためです。LOOKING 状態を見てみましょう。 。

case LOOKING:
// If notification > current, replace and send messages out
  //收到的epoch是不是比当前选举的epoch要大,如果大那么代表是新一轮选举
  if (n.electionEpoch > logicalclock.get()) {
    
    
      logicalclock.set(n.electionEpoch);  //更新当前epoch
      recvset.clear();  //情况收到的投票
      //进行投票

      /*
       * We return true if one of the following three cases hold:
       * 1- New epoch is higher
       * 收到的epoch大于当前的epoch 胜出选举
       * 2- New epoch is the same as current epoch, but new zxid is higher
       * 如果收到的epoch等于当前epoch,那么收到的zxid大于当前zxid胜出选举
       * 3- New epoch is the same as current epoch, new zxid is the same
       *  as current zxid, but server id is higher.
       * 如果收到的epoch等于当前epoch,zxid登录当前zxid,
       * 那么收到的myid大于当前myid的胜出选举
       */
      if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
              getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
    
    
          updateProposal(n.leader, n.zxid, n.peerEpoch); //把胜出的消息更新到投票提议中
      } else {
    
      //如果收到消息没有胜出,那么选择当前的消息更新到投票提议中
          updateProposal(getInitId(),
                  getInitLastLoggedZxid(),
                  getPeerEpoch());
      }
      sendNotifications();  //发送投票消息
  } 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;
      //如果收到的逻辑时钟相等,则去对比myid 、zxid、epoch
  } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
          proposedLeader, proposedZxid, proposedEpoch)) {
    
    
      updateProposal(n.leader, n.zxid, n.peerEpoch);
      sendNotifications();
  }

  if(LOG.isDebugEnabled()){
    
    
      LOG.debug("Adding vote: from=" + n.sid +
              ", proposed leader=" + n.leader +
              ", proposed zxid=0x" + Long.toHexString(n.zxid) +
              ", proposed election epoch=0x" + Long.toHexString(n.electionEpoch));
  }

  //把投票结果存到本地,用来做最终判断
  recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

  //判断选举是否结束,默认算法过半同意
  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){
    
    
          if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                  proposedLeader, proposedZxid, proposedEpoch)){
    
    
              recvqueue.put(n);//获得最新的记过
              break;
          }
      }

      /*
       * This predicate is true once we don't read any new
       * relevant message from the reception queue
       */
      if (n == null) {
    
    
          self.setPeerState((proposedLeader == self.getId()) ?
                  ServerState.LEADING: learningState());

          Vote endVote = new Vote(proposedLeader,
                  proposedZxid, proposedEpoch);
          leaveInstance(endVote);
          return endVote;
      }
  }
  break;


1. 受信したエポックは現在の選挙エポックより大きいですか? 大きい場合は、新しい選挙ラウンドを意味します。小さい場合、単語は次のことを表します受け取った票は無効であり、受け取った票はクリアされます。
2. 次に、選出アルゴリズムを実行します。

  • 受信したエポックが現在のエポックより大きく、選挙に勝ちます
  • 受信したエポックが現在のエポックと等しい場合、受信した zxid は現在の zxid より大きく、選挙に勝ちます。
  • 受信したエポックが現在のエポックと等しく、zxid も現在の zxid と等しい場合、受信した myid は現在の myid の勝利した選択よりも大きくなります。

以下のコードを見てみましょう。
totalOrderPredicate() メソッドはアルゴリズムのエントリです

/*
     * We return true if one of the following three cases hold:
        * 1- New epoch is higher
        * 收到的epoch大于当前的epoch 胜出选举
        * 2- New epoch is the same as current epoch, but new zxid is higher
        * 如果收到的epoch等于当前epoch,那么收到的zxid大于当前zxid胜出选举
        * 3- New epoch is the same as current epoch, new zxid is the same
        *  as current zxid, but server id is higher.
        * 如果收到的epoch等于当前epoch,zxid登录当前zxid,
       * 那么收到的myid大于当前myid的胜出选举
       */
       if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
              getInitId(), getInitLastLoggedZxid(), getPeerEpoch())) {
     
     
          updateProposal(n.leader, n.zxid, n.peerEpoch); //把胜出的消息更新到投票提议中
       } else {
     
       //如果收到消息没有胜出,那么选择当前的消息更新到投票提议中
           updateProposal(getInitId(),
                   getInitLastLoggedZxid(),
                   getPeerEpoch());
       }

このアルゴリズムを見てみましょう

protected boolean totalOrderPredicate(long newId, long newZxid,
		 long newEpoch, long curId, long >curZxid, long curEpoch) {
     
     
       LOG.debug("id: " + newId + ", proposed id: " + curId + ", zxid: 0x" +
               Long.toHexString(newZxid) + ", proposed zxid: 0x" +
                Long.toHexString(curZxid));
       if(self.getQuorumVerifier().getWeight(newId) == 0){
     
     
          return false;
       }
      /*
      * We return true if one of the following three cases hold:
      * 1- New epoch is higher
      * 2- New epoch is the same as current epoch, but new zxid is higher
      * 3- New epoch is the same as current epoch, new zxid is the same
      *  as current zxid, but server id is higher.
      */

     return ((newEpoch > curEpoch) ||
              ((newEpoch == curEpoch) &&
               ((newZxid > curZxid) || ((newZxid == curZxid) && (newId > curId)))));
   }

リターンコードを見ましたか? わかりやすいでしょうか。
まだ終わっていません。ここでは選挙アルゴリズムの判定を確認するだけです。判定が終了した後、投票が受け取られて勝者が得られた場合、受け取った投票に応じて提案が更新されます。受信したメッセージが当選しなかった場合は、現在のメッセージを選択して投票提案に更新します
。更新後、sendNotifications(); メソッドを実行して投票メッセージを送信します。
終了した?下を向き続けなかった

994行目付近にこのようなコード行があります

recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));

recvset このことを覚えていますか? 最初に 2 つの HashMap が定義され、1 つは受け取った投票を保存し、もう 1 つは投票結果を保存してから、
別の判断を行います。

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){
     
     
	         if(totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
	                 proposedLeader, proposedZxid, proposedEpoch)){
     
     
	             recvqueue.put(n);//获得最新的记过
	             break;
	         }
	     }
	
	     /*
	      * This predicate is true once we don't read any new
	      * relevant message from the reception queue
	      */
	     if (n == null) {
     
     
	         self.setPeerState((proposedLeader == self.getId()) ?
	                 ServerState.LEADING: learningState());
	
	         Vote endVote = new Vote(proposedLeader,
	                 proposedZxid, proposedEpoch);
	         leaveInstance(endVote);
	         return endVote;
	     }
	 }

termPredicate メソッドは何をするのでしょうか? このメソッドの入力パラメータが何であるかを見てみましょ

。理解できたら
、termPredicate メソッドを見てみましょう

private boolean termPredicate(HashMap<Long, Vote> votes, Vote vote) {
     
     
      SyncedLearnerTracker voteSet = new SyncedLearnerTracker();
       voteSet.addQuorumVerifier(self.getQuorumVerifier());
      if (self.getLastSeenQuorumVerifier() != null
             && self.getLastSeenQuorumVerifier().getVersion() > self
                      .getQuorumVerifier().getVersion()) {
     
     
           voteSet.addQuorumVerifier(self.getLastSeenQuorumVerifier());
       }

      /*
       * First make the views consistent. Sometimes peers will have different zxids for a server depending >on timing.
       */
     //遍历已经收到的投票结果是否有等于当前投票提议的。如果有把当前投票放入到ack中
    for (Map.Entry<Long, Vote> entry : votes.entrySet()) {
     
     
        if (vote.equals(entry.getValue())) {
     
     
           voteSet.addAck(entry.getKey());
      }
   }
  //判断票数是否过半
 return voteSet.hasAllQuorums();
}

このメソッドは 2 つの重要なことを行います。
1. voteSet.addAck(entry.getKey()) は、
受信した投票結果が現在の投票提案と等しいかどうかを調べます。現在の投票が voteSet 2 の ack に入れられる場合
、voteSet.hasAllQuorums(); は、voteSet の半分以上が
SyncedLearnerTracker クラスであるかどうかを判断します。

まず、voteSet.addAck(entry.getKey()) を見てみましょう。

public boolean addAck(Long sid) {
     
     
      boolean change = false;
      for (QuorumVerifierAcksetPair qvAckset : qvAcksetPairs) {
     
     
           if (qvAckset.getQuorumVerifier().getVotingMembers().containsKey(sid)) {
     
     
              qvAckset.getAckset().add(sid);
               change = true;
           }
       }
       return change;
   }

この判定の目的は無視しましょう。判定の内容 qvAckset.getAckset().add(sid); を見て、それが HashSet を返す
静的内部クラス getAckset() メソッドであることを確認します。 HashSet の add メソッドを呼び出します。

public static class QuorumVerifierAcksetPair {
     
     
     private final QuorumVerifier qv;
    private final HashSet<Long> ackset;

     public QuorumVerifierAcksetPair(QuorumVerifier qv, HashSet<Long> ackset) {
     
                     
        this.qv = qv;
        this.ackset = ackset;
    }

     public QuorumVerifier getQuorumVerifier() {
     
     
        return this.qv;
    }

     public HashSet<Long> getAckset() {
     
     
         return this.ackset;
     }
  }

これで、 voteSet.addAck(entry.getKey()); が何をするか、つまり、entry.getKey() を ackset と呼ばれる HashMap に入れることが分かりました。entry.getKey() が何であるか覚えていますか? 私たちが受け取った投票の側です

voteSet.addAck(entry.getKey()) を知ってから、voteSet.hasAllQuorums() を調べます

public boolean hasAllQuorums() {
     
     
       for (QuorumVerifierAcksetPair qvAckset : qvAcksetPairs) {
     
     
           if (!qvAckset.getQuorumVerifier().containsQuorum(qvAckset.getAckset()))
               return false;
       }
       return true;
   }

この判断については楽観的であるため、containsQuorum メソッドはここで委任モードを使用し、判断を行うために QuorumMaj クラスに委任されます。受信パラメータは ackset で、これは先ほど見た HashSet です。

4 つの変数を定義する QuorumMaj クラスを見てみましょう。

private Map<Long, QuorumServer> allMembers =
 				new HashMap<Long, QuorumServer>();
private HashMap<Long, QuorumServer> votingMembers = 
				new HashMap<Long, QuorumServer>();
private HashMap<Long, QuorumServer> observingMembers = 
				new HashMap<Long, QuorumServer>();
private long version = 0;
private int half;

これら 5 つの変数サブテーブルの意味は次のとおりです。
1.allMembers は、このクラスター内のすべてのマシンのコレクションを示します。
2.votingMembers は、リーダーとフォロワーを含む、このクラスター内の投票マシンのコレクションを示します。
3.observingMembers は、このクラスター内のオブザーバーのコレクションを示します。クラスタ
4. バージョンは検証を示します バージョン
5. デバイスの半分は投票セットの総数を表します

次に、先ほど述べた containsQuorum メソッドが何を行うかを見てみましょう。

public boolean containsQuorum(Set<Long> ackSet) {
     
     
       return (ackSet.size() > half);
   }

ここでは ackSet のサイズがhalfより大きいかどうかを判断しますが
、この半分は何に代入するのでしょうか?もちろん、これは私たちの構築方法です。
私たちの QuorumMaj クラスには 100 行を超えるコードしかありません。それを探して、半分に値が割り当てられているのを確認してください。

public QuorumMaj(Map<Long, QuorumServer> allMembers) {
     
     
       this.allMembers = allMembers;
       for (QuorumServer qs : allMembers.values()) {
     
     
           if (qs.type == LearnerType.PARTICIPANT) {
     
     
               votingMembers.put(Long.valueOf(qs.id), qs);
           } else {
     
     
              observingMembers.put(Long.valueOf(qs.id), qs);
           }
       }
      half = votingMembers.size() / 2;
   }

ここはどうやって判断すればいいのでしょうか?
1. パラメータが allMembers コレクションの場合、LearnerType に従って VotingMembers コレクションに属するか、observingMembers に属するかを判断し、半分は VotingMembers.size() / 2 2. パラメータが設定ファイルを解析して生成された Properties オブジェクトの
場合、serverId とロールを解析し、対応するマップに保存します。


containsQuorum メソッドにある ackSet.size() >half は、投票数が半分を超えている場合に true を返すことを意味していることがわかります

これは、zk クラスターが正常に動作するためには、(n/2 + 1) 台のマシンが正常に動作していることを確認する必要があることも検証します。

概要:
zk 選出アルゴリズム:
1. 投票の処理
受信したエポックが現在のエポックよりも大きい
場合、受信したエポックが現在のエポックと等しい場合、受信した zxid が現在の zxid より大きい場合、勝ちとなります
。現在のエポックでは、zxid は現在の zxid にログインします。受信した myid が現在の myid より大きい場合、
2 が勝ち、勝者が選択され、
最も多くの票を獲得したものが選挙に勝ちます。

这里我们只看了zk集群启动的时候怎么进行选举的,并没有看leader宕机之后的选举过程,其实差别不大。
好了我们的zk选举的内容就先看到这里吧,有很多东西我们没有去看,
比如说我们怎么把投票协议发送出去。。不过这些并不影响我们去阅读源码。
阅读源码有时候没必要很深入,很深入的话会影响我们的判断,等我们熟悉读源码的节奏之后再去深入也不迟

欠点を指摘してください、ありがとう

おすすめ

転載: blog.csdn.net/u010994966/article/details/95937323
おすすめ