Zookeeper实现选举 - 源码解读

本文深入剖析Zookeeper的选举机制,从使用场景出发,解释了如何通过观察者模式实现选举流程的回调管理。详细解读了发起投票、决定选举结果的关键源码,展示了在节点挂掉时如何自动重新选举以确保集群的高可用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

选举的使用场景

在分布式环境下,为了保证系统的高可用性,我们必须消除单点,其中一个主要的解决方案就是建立集群。集群有多个提供相同服务的机器组成,在这样场景下,又会出现一致性问题,原因是我们无法保证多台机子上的数据是一致的。在这种情况下,我们可以通过一主多从的方案,通过主节点来进行读写操作,从节点只进行读操作的方式,通过主从复制来尽可能保证一致性。那么如何来决定谁来当主节点呢?

这就是我们今天要讲的 — 选举

设计模式

在zookeeper选举的逻辑中,由于涉及到诸多的状态,zookeeper采用了观察者模式,来执行不同状态下的回调,实现了对 回调逻辑 与 主流程之间的耦合,即如果我要增加当zookeeper选举结束后的逻辑,此时我不需要修改zookeeper选举的主流程代码。

源码解读

    public synchronized void start() {
        // 执行选举开始前的回调
        state = State.START;
        dispatchEvent(EventType.START);

        // 非空校验
        if (zooKeeper == null) {
            throw new IllegalStateException(
                "No instance of zookeeper provided. Hint: use setZooKeeper()");
        }
        if (hostName == null) {
            throw new IllegalStateException(
                "No hostname provided. Hint: use setHostName()");
        }

        try {
            // 发起投票
            makeOffer();
            // 决定选举结果
            determineElectionStatus();
        } catch (KeeperException | InterruptedException e) {
            becomeFailed(e);
        }
    }

发起投票:

    private void makeOffer() throws KeeperException, InterruptedException {
        // 执行投票开始的回调
        state = State.OFFER;
        dispatchEvent(EventType.OFFER_START);

        LeaderOffer newLeaderOffer = new LeaderOffer();
        byte[] hostnameBytes;
        synchronized (this) {
            newLeaderOffer.setHostName(hostName);
            hostnameBytes = hostName.getBytes();
            // 开始投票,创建临时顺序节点,这里是选举的关键,保证了多个节点创建的临时节点是不一样的,按顺序的
            newLeaderOffer.setNodePath(zooKeeper.create(rootNodeName + "/" + "n_",
                                                        hostnameBytes, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                                                        CreateMode.EPHEMERAL_SEQUENTIAL));
            leaderOffer = newLeaderOffer;
        }
        LOG.debug("Created leader offer {}", leaderOffer);
        // 执行投票结束的回调
        dispatchEvent(EventType.OFFER_COMPLETE);
    }

决定选举结果

    private void determineElectionStatus() throws KeeperException, InterruptedException {
        // 执行选举开始的回调
        state = State.DETERMINE;
        dispatchEvent(EventType.DETERMINE_START);

        // 获取当前节点
        LeaderOffer currentLeaderOffer = getLeaderOffer();

        String[] components = currentLeaderOffer.getNodePath().split("/");

        // id为hostName
        currentLeaderOffer.setId(Integer.valueOf(components[components.length - 1].substring("n_".length())));

        // 收集选票,获取投票箱内的选票信息(这里的票是没有顺序的)
        List<LeaderOffer> leaderOffers = toLeaderOffers(zooKeeper.getChildren(rootNodeName, false));

        /*
         * For each leader offer, find out where we fit in. If we're first, we
         * become the leader. If we're not elected the leader, attempt to stat the
         * offer just less than us. If they exist, watch for their failure, but if
         * they don't, become the leader.
         */
        // 唱票阶段
        for (int i = 0; i < leaderOffers.size(); i++) {
            LeaderOffer leaderOffer = leaderOffers.get(i);

            if (leaderOffer.getId().equals(currentLeaderOffer.getId())) {
                // 执行选举结束回调
                dispatchEvent(EventType.DETERMINE_COMPLETE);
                // 如果你是第一张票,你就是leader,否则就是follower
                if (i == 0) {
                	// 成为leader
                    becomeLeader();
                } else {
                	// 成为follower
                    becomeReady(leaderOffers.get(i - 1));
                }

                /* Once we've figured out where we are, we're done. */
                break;
            }
        }
    }

我们来看看成为leader和成为follower之后的逻辑:

	// 成为leader的逻辑很简单,什么都不做,只执行可能存在的回调操作
    private void becomeLeader() {
        state = State.ELECTED;
        dispatchEvent(EventType.ELECTED_START);

        LOG.info("Becoming leader with node: {}", getLeaderOffer().getNodePath());

        dispatchEvent(EventType.ELECTED_COMPLETE);
    }
    private void becomeReady(LeaderOffer neighborLeaderOffer) throws KeeperException, InterruptedException {

        // 对前一个邻居节点创建监听,当前一个邻居节点宕机,则会执行回调.(下面会将回调内容)
        Stat stat = zooKeeper.exists(neighborLeaderOffer.getNodePath(), this);

        if (stat != null) {
            // 创建监听成功,执行回调
            dispatchEvent(EventType.READY_START);
            state = State.READY;
            dispatchEvent(EventType.READY_COMPLETE);
        } else {
            /*
             * If the stat fails, the node has gone missing between the call to
             * getChildren() and exists(). We need to try and become the leader.
             */
            // 如果创建监听失败,我们认为前一个相邻节点可能在选举过程中被删除了,我们需要重新选举
            determineElectionStatus();
        }

    }

我们来看一下从节点对前一个相邻节点的监听回调:
类LeaderElectionSupport是实现了Watch接口的,里面的process实现如下:

    @Override
    public void process(WatchedEvent event) {
        // 如果监听到前面的相邻节点挂掉了(被移除了)
        if (event.getType().equals(Watcher.Event.EventType.NodeDeleted)) {
            // 一般都不相等
            if (!event.getPath().equals(getLeaderOffer().getNodePath())
                && state != State.STOP) {
                LOG.debug(
                    "Node {} deleted. Need to run through the election process.",
                    event.getPath());
                try {
                    // 尝试进行重新选举主节点
                    determineElectionStatus();
                } catch (KeeperException | InterruptedException e) {
                    becomeFailed(e);
                }
            }
        }
    }

可见,在zookeeper集群中,即使一个从节点挂掉了,zookeeper集群也会进行重新选举。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值