Zookeeper(FastLeaderElection选主流程详解)

触发场景

Zookeeper的核心是原子广播(Zab:Zookeeper Atomic Broadcast),该机制保证各个Server之间的同步。Zab协议有两种模式,分别是恢复模式和广播模式。其中恢复模式就代表我们的leader选举流程,在Zookeeper集群中有两种情况需要进入leader选举:

  1. Server初始化启动
  2. 集群中Leader挂掉了

集群服务器状态

状态(state) 定义 描述
LOOKING 不确定Leader状态 该状态下的服务器认为当前集群中没有Leader,会发起Leader选举
FOLLOWING  跟随者状态 表明当前服务器角色是Follower,并且它知道Leader是谁
LEADING 领导者状态 表明当前服务器角色是Leader,它会维护与Follower间的心跳
OBSERVING 观察者状态 表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票

选票名词

  • state: 当前服务器的状态(LOOKING,FOLLOWING ,LEADING,OBSERVING)
  • logicClock: 每个服务器会维护一个自增的整数,名为logicClock,它表示这是该服务器发起的第多少轮投票
  • sid: 投票服务器的myid,dataDir下的myid文件配置
  • vote_id: 被推举方服务器myid
  • vote_zxid:  被推举方服务器事务id,集群产生一个事务时,就会为该事务分配一个标识符,也就是 zxid。zxid 是一个 long 型(64位)整数, 低32代表一个单调递增的计数器,高32位代表Leader周期。
  • recvset: 收到投票集合即票箱,Map其中key记录sid,vote存放选票实体数据即被推举方服务器数据

 投票流程

FastLeaderElection算法(源码)

可通过electionAlg配置项设置Zookeeper用于领导选举的算法,一般使用Zookeeper的默认算法FastLeaderElection

其中核心源码lookForLeader()方法如下:

public Vote lookForLeader() throws InterruptedException {
        //计算选举的耗时
        if (self.start_fle == 0) {
            self.start_fle = Time.currentElapsedTime();
        }
        try {
            //选票票箱
            Map recvset = new HashMap();
            Map outofelection = new HashMap();
            //接收消息的时间
            int notTimeout = minNotificationInterval;
            synchronized (this) {
                logicalclock.incrementAndGet();
                //先投给自己
                updateProposal(getInitId()/** 被推举服务器myid(vote_id) **/, getInitLastLoggedZxid() /** 被推举服务器zxid(vote_zxid) **/, getPeerEpoch() /** 投票轮次(logicClock) **/);
            }
            //发送投票,包括发给自己
            sendNotifications();
            SyncedLearnerTracker voteSet;
            //主循环,直到选出leader
            while ((self.getPeerState() == ServerState.LOOKING) &&
                    (!stop)) {
                //从IO线程里拿到投票消息,自己的投票也在这里处理
                //LinkedBlockedQueue() 接收
                Notification n = recvqueue.poll(notTimeout,
                        TimeUnit.MILLISECONDS);
                if (n == null) {//没有获取到选票
                    //如果空闲情况,消息发完了,继续发送,一直到选出leader为止
                    if (manager.haveDelivered()) {
                        sendNotifications();
                    } else {
                        //消息还没投递出去,可能是其他server 还没启动,尝试再连接
                        manager.connectAll();
                    }
                    //延长超时时间
                    int tmpTimeOut = notTimeout * 2;
                    notTimeout = (tmpTimeOut < maxNotificationInterval ?
                            tmpTimeOut : maxNotificationInterval);
                    LOG.info("Notification time out: " + notTimeout);
                } else if (validVoter(n.sid) && validVoter(n.leader)) {//收到一个有效的选票结果
                    switch (n.state) {
                        case LOOKING: //发送消息放的节点状态LOOKING
                            if (getInitLastLoggedZxid() == -1) {
                                LOG.debug("Ignoring notification as our zxid is -1");
                                break;
                            }
                            if (n.zxid == -1) {
                                LOG.debug("Ignoring notification from member with -1 zxid" + n.sid);
                                break;
                            }
                            // If notification > current, replace and send messages out
                            //判断收到消息投票的轮次epoch(接收消息的投票轮次大于自己的轮次)
                            if (n.electionEpoch > logicalclock.get()) {
                                //更新本地的投票的轮次epoch
                                logicalclock.set(n.electionEpoch);
                                //清空接收队列
                                recvset.clear();
                                //检查接收到的这个消息是否可以胜出,logicClock/peerEpoch(选举轮次),vote_zxid/zxid(事物id),vote_id/leader(被推举服务器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()) {
                                //如果收到的消息epoch小于当前节点的epoch,则忽略这条消息
                                if (LOG.isDebugEnabled()) {
                                    //...
                                }
                                break;
                            } else if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                    proposedLeader, proposedZxid, proposedEpoch)) {
                                //如果是epoch相同的话,就是比较zxid,myid,如果胜出,则更新自己的票据,并且发出广播
                                updateProposal(n.leader, n.zxid, n.peerEpoch);
                                sendNotifications();
                            }
                            // don't care about the version if it's in LOOKING state
                            recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
                            // 把与自己当前的选票相同的票加到voteSet中去
                            voteSet = getVoteTracker(
                                    recvset, new Vote(proposedLeader, proposedZxid,
                                            logicalclock.get(), proposedEpoch));
                            //收到了大多数的选票
                            if (voteSet.hasAllQuorums()) {
                                // Verify if there is any change in the proposed leader
                                //一直等到新的notification的到达,直到超时
                                while ((n = recvqueue.poll(finalizeWait,
                                        TimeUnit.MILLISECONDS)) != null) {
                                    if (totalOrderPredicate(n.leader, n.zxid, n.peerEpoch,
                                            proposedLeader, proposedZxid, proposedEpoch)) {
                                        recvqueue.put(n);
                                        break;
                                    }
                                }
                                // 超时时间内没有新的选票了
                                if (n == null) {
                                    //选举结束,设置当前节点的状态
                                    setPeerState(proposedLeader, voteSet);
                                    //返回最终投票结果
                                    Vote endVote = new Vote(proposedLeader,
                                            proposedZxid, logicalclock.get(),
                                            proposedEpoch);
                                    leaveInstance(endVote);
                                    return endVote;
                                }
                            }
                            break;
                        case OBSERVING:
                            //如果收到的消息选票状态不是LOOKING,比如这台机器刚加入一个已经正在运行的ZK集群时//observer 不参与选举
                            LOG.debug("Notification from observer: " + n.sid);
                            break;
                        case FOLLOWING:
                        case LEADING:
                            //说明当前系统中已经存在Leader了,直接同步Leader
                            if (n.electionEpoch == logicalclock.get()) {//判断epoch是否相同
                                //加入到本机的投票集合
                                recvset.put(n.sid, new Vote(n.leader, n.zxid, n.electionEpoch, n.peerEpoch));
                                //投票是否结束,如果结束,再确认Leader是否有效
                                //如果结束,修改自己的状态并返回投票结果
                                voteSet = getVoteTracker(recvset, new Vote(n.version,
                                        n.leader, n.zxid, n.electionEpoch, n.peerEpoch, n.state));
                                if (voteSet.hasAllQuorums() &&
                                        checkLeader(outofelection, n.leader, n.electionEpoch)) {
                                    setPeerState(n.leader, voteSet);
                                    Vote endVote = new Vote(n.leader,
                                            n.zxid, n.electionEpoch, n.peerEpoch);
                                    leaveInstance(endVote);
                                    return endVote;
                                }
                            }
                            outofelection.put(n.sid, new Vote(n.version, n.leader,
                                    n.zxid, n.electionEpoch, n.peerEpoch, n.state));
                            voteSet = getVoteTracker(outofelection, new Vote(n.version,
                                    n.leader, n.zxid, n.electionEpoch, n.peerEpoch, n.state));

                            if (voteSet.hasAllQuorums() &&
                                    checkLeader(outofelection, n.leader, n.electionEpoch)) {
                                synchronized (this) {
                                    logicalclock.set(n.electionEpoch);
                                    setPeerState(n.leader, voteSet);
                                }
                                Vote endVote = new Vote(n.leader, n.zxid,
                                        n.electionEpoch, 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 {
            //jmx相关处理...
        }
    }

分析选举源码步骤

  1. 开始选举:选举开始自增选举轮次即logicClock++
  2. 清空票箱:清空recvset集合
  3. 发送初始化选票:开始选票时每个服务器节点把第一个选票投给自己,记录到票箱并广播
  4. 接收外部选票:从IO线程里拿到其他服务器节点的投票消息对象(Notification)
  5. 选票PK: 用自己的选票跟接收到的外部选票信息比较pk核心方法totalOrderPredicate,依次比较peerEpoch/logicClock(选票轮次)、zxid(被推举服务器事务id)、leader/vote_id(被推举服务器myid);如果接收到的外部选票信息能够胜出则把自己的选票改外对方的投票,并把自己接收的选票信息和自己更新后的选票记录入票箱。
  6. 统计选票:统计票箱中的选票数据,如果已经确定有过半服务器认可了自己的投票信息则终止投票。否则继续广播自己的投票或者接收外部选票信息。
  7. 更新服务器状态:投票终止后,服务器开始更新自身状态。若获取过半服务器选票的支持则把自己的状态更新为LEADING,否则更新自己的状态为FOLLOWING。
  8. 开始广播模式:集群确定了LEADING后,恢复模式结束开启广播模式保证各个节点间数据的同步。

 

两种选举流程触发场景

一.Server初始化启动

集群刚启动时,所有服务器的logicClock都为1,zxid都为0;所有的服务器发送初始化选票,并把自己的选票记录到票箱。如下图:每个Server票箱中都是自己的选票,并把自己的选票信息广播到其他服务器中;广播的选票信息包括sid(投票服务器的myid)、logicClock(选票轮次)、vote_id(被推举服务器myid)、vote_zxid(被推举服务器的事务id);

Zookeeper(FastLeaderElection选主流程详解)_第1张图片

在上图中Server1收到Server2和Server3的选票后,Server1的选票信息就和Server2和Server3发生选票pk;由于集群刚启动所有的logicClock和zxid都相同所以根据vote_id判断,Server3中的vote_id比Server1中的vote_id大,所以Server3胜出;Server1更新自己的选票信息推举Server3,并把更新后的选票和Server3的选票记录入票箱;然后将自己更新后的选票信息广播出去,此时Server1票箱recvset={(3,3),(1,3)}

Server2收到Server3的选票后同理吧更新自己的选票,并且Server1票箱recvset={(3,3),(2,3)}

Server3收到选票后根据选票pk规则无需更新选票recvset={(3,3)}

Server1,Server2更新选票后都推举Server3并且广播选票,这时3个服务的选票都是推荐Server3,并且3个票箱都是recvset={(3,3),(2,3),(1,3)},此时Server3就为Leader,如下图:

Zookeeper(FastLeaderElection选主流程详解)_第2张图片

二.集群中Leader挂掉了

 Server3为集群中的LEADER,当Server3出现故障发生了宕机;Server1,Server1立即进入LOOKING状态并发起选举流程,初始化选票如下图:Server1和Server2分别推荐自己成为LEADER。

Zookeeper(FastLeaderElection选主流程详解)_第3张图片

Server1接收到Server2的选票由于logicClock相同Server1的vote_id=101大于Server2的vote_id=100,所以Server1选票不变;

Server2接收到Server1的选票根据上面规则推举Server1成为LEADER,更新选票和票箱并且广播自己更新的选票;此时Server1和Server2都推荐Server为LEADER。

当Server3修复启动后进入LOOKING状态,发现Server1为LEADER后则进入FOLLOWER状态

 

你可能感兴趣的:(Zookeeper(FastLeaderElection选主流程详解))