[Zookeeper] 选举流程Fast Leader

选举信息-选举流程-选举场景-源码分析

1 选举信息

<1> 服务器角色信息
在Zookeeper集群提供服务时,集群中角色如下:

  • Leader:一个Zookeeper集群同一时间只有一个Leader。所有的写操作必须要通过Leader完成,再由Leader将写操作广播给其它服务器。
  • Follower:一个Zookeeper集群可以存在多个Follower。Follower可直接处理并且返回客户端的读请求,同时会将写请求转发给Leader处理,并且负责在Leader处理写请求时对请求进行投票。另外Follower可以参与竞选Leader。
  • Observer:Observer功能与Follower类似,但是没有投票权,也不会参与竞选Leader。

<2> 服务器状态信息

  • Looking:寻找Leader状态。当服务器处于该状态时,会认为当前集群中没有Leader,因此需要进入Leader选举流程
  • Following:跟随者状态,表明当前服务器角色是Follower
  • Leading:领导者状态,表明当前服务器角色是Leader
  • Observing:观察者状态,表明当前服务器角色是Observer

<3> 投票信息

  • leader:被选举的Leader的sid
  • zxid:被选举的Leader的事务id
  • sid:当前服务器的sid
  • electionEpoch:当前投票的轮次
  • peerEpoch:当前服务器的Epoch

选票PK:
(1)选票中Epoch大的优先级高;
(2)选票中Zxid的大的优先级高;
(3)选票中Sid大的优先级高;

选票终止条件:
以某一选票数占集群中参与竞选节点(除Observer外)数量的一半以上,选举结束;

2 选举流程

选举流程

3 选举场景

<1> 票箱信息
票箱信息:保存选举的服务器SID和被选举的服务器SID,即(sid,leader);例如集群中节点为SID=1的服务器选举节点为SID=3的服务器,则票箱信息为(1,3)

<2> 选票信息
选票信息为(electionEpoch,leader,zxid),分别代表选举的轮次、被选举服务器的SID,被选举服务器的zxid

<3> 初始启动选举
(1)每个Server发出一个投票,初始情况,Server都会将自己作为Leader服务器来进行投票,比如Server1会发出(1,1,0)选票(表示epoch为1,选举leader的sid为1,并且被选举的服务器zxid为0),然后各自将这个投票发给集群中其他机器,Server1的票箱信息为(1,1)(表示投票的服务器sid为1,选举的leader的sid为1)
(2)接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票,是否来自Looking状态的服务器
(3)处理投票。针对每一个投票,服务器需要将别人的票和自己的票进行PK,pk规则如上所示。
(4)统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。
(5)改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。

<4> Follower重启

<5> 运行期间选举 leader宕机
与上面相比,会在开始添加一个步骤【变更状态】。
Leader挂后,余下的非Observer服务器都会讲自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。

4 源码分析

4.1 类图关系

类图关系

4.1 FastLeaderElection

<1> Notification

Notification表示收到的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息,其buildMsg方法将选举信息封装至ByteBuffer中再进行发送。

<2> ToSend

ToSend表示发送给其他服务器的选举投票信息,也包含了被选举者的id、zxid、选举周期等信息。

<3> Messenger
其中 WorkerReceiver:

选票接收器,不断地从QuorumCnxManager中获取其他服务器发来的选举消息,并将其转换成一个选票,然后保存到recvqueue中

其中 WorkerSender:

选票发送器,其会不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中,其过程是将FastLeaderElection的ToSend转化为QuorumCnxManager的Message

4.2 QuorumCnxManager

该类有四个内部类:
SendWorker类,Message类,RecWorker类,Listener类
<1> SendWorder
这个类作为“发送者”,继承ZooKeeperThread,线程不断地从发送队列取出,发送给对应sid的机器。

        Long sid; //目标机器sid,不是当前机器sid
        Socket sock;
        RecvWorker recvWorker; //该sid对应的RecWorker
        volatile boolean running = true;
        DataOutputStream dout;

<2> Message

    static public class Message {
        Message(ByteBuffer buffer, long sid) {
            this.buffer = buffer;
            this.sid = sid;
        }

        ByteBuffer buffer;
        long sid;
    }

sid为消息来源方的sid,buffer即指消息体

<3> RecvWorker

        Long sid;
        Socket sock;
        volatile boolean running = true;
        final DataInputStream din;
        final SendWorker sw;

4.3 FastLeaderElection中的lookForLeader()

    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 recvset = new HashMap();

            HashMap outofelection = new HashMap();

            int notTimeout = finalizeWait;

            synchronized(this){
                // 逻辑时钟自增,每进行一轮新的Leader选举,都需要更新逻辑时钟
                logicalclock.incrementAndGet();
                // 更新选票(初始化选票)
                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)
            while ((self.getPeerState() == ServerState.LOOKING) &&
                    (!stop)){
                /*
                 * Remove next notification from queue, times out after 2 times
                 * the termination time
                 */
                // 从接受到的选票队列中拿取一个 Notification
                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();
                    }

                    /*
                     * Exponential backoff
                     */
                    int tmpTimeOut = notTimeout*2;
                    notTimeout = (tmpTimeOut < maxNotificationInterval?
                            tmpTimeOut : maxNotificationInterval);
                    LOG.info("Notification time out: " + notTimeout);
                }
                // 选票接收队列中存在选票并且该选票的发送者是有资格投票的
                else if(self.getVotingView().containsKey(n.sid)) {
                    /*
                     * Only proceed if the vote comes from a replica in the
                     * voting view.
                     */
                    // 判断发送选票的服务器的状态
                    switch (n.state) {
                    case LOOKING: // 处于寻找leader状态
                        // If notification > current, replace and send messages out
                        // 发出选票的服务器epoch大于本服务器的逻辑时钟
                        if (n.electionEpoch > logicalclock.get()) {
                            // 改变逻辑时钟的值
                            logicalclock.set(n.electionEpoch);
                            // TODO 清空票箱
                            recvset.clear();
                            // 进行选票pk
                            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()) { // 发出选票的服务器epoch小于本服务器的逻辑时钟
                            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; // 直接跳出循环了??? 这里是结束switch循环,重新获取一张选票
                        } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch, // 发出选票的服务器epoch等于本服务器的逻辑时钟,进行pk,如果获胜则更新选票并广播
                                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));
                        // 判断选举是否结束
                        // 票箱和当前的leader选票进行比较,看是否超过半数
                        if (termPredicate(recvset,
                                new Vote(proposedLeader, proposedZxid,
                                        logicalclock.get(), proposedEpoch))) {

                            // Verify if there is any change in the proposed leader
                            // 将选票接收队列中所有剩下的选票与选出的leader比较,如果获胜,则放入票箱,跳出while循环
                            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) {
                                // 最后胜出的选票是自己,更新状态为leading,否则为following
                                self.setPeerState((proposedLeader == self.getId()) ?
                                        ServerState.LEADING: learningState());

                                Vote endVote = new Vote(proposedLeader,
                                                        proposedZxid,
                                                        logicalclock.get(),
                                                        proposedEpoch);
                                leaveInstance(endVote);
                                return endVote;
                            }
                        }
                        break;
                    case OBSERVING:
                        LOG.debug("Notification from observer: " + n.sid);
                        break;
                    // 发送选票的服务器的状态是Following和Leading
                    // 这种情况是某台服务器重启之后,已经选举出新Leader了
                    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(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集合中,再根据outofelection来判断是否可以结束选举,
                        // 如果可以也是保存逻辑时钟,设置选举状态,退出选举过程。
                        outofelection.put(n.sid, new Vote(n.version,
                                                            n.leader,
                                                            n.zxid,
                                                            n.electionEpoch,
                                                            n.peerEpoch,
                                                            n.state));
           
                        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;
                    }
                } else {
                    LOG.warn("Ignoring notification from non-cluster member " + 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());
        }
    }
}

流程图:

你可能感兴趣的:([Zookeeper] 选举流程Fast Leader)