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<Long,Vote>其中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<Long/** 发送方的myid(sid) **/, Vote /** 选票实体 **/> recvset = new HashMap<Long, Vote>();
            Map<Long, Vote> outofelection = new HashMap<Long, Vote>();
            //接收消息的时间
            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);

在上图中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,如下图:

二.集群中Leader挂掉了

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

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状态

 

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值