美文网首页
原创-zookeeper集群优先选择连接节点改造

原创-zookeeper集群优先选择连接节点改造

作者: 无色的叶 | 来源:发表于2019-05-28 16:16 被阅读0次

背景

zookeeper集群跨机房部署,采用observer部署模式,达到目标如下:

  • A机房zk节点参与选举
  • B机房zk节点只负责同步节点信息,不参与选举
  • A机房zk节点全挂掉后,切换到B机房zk集群,zk数据不会丢失


    image.png

问题

zk集群是跨机房部署,会存在部署在A机房的应用连接到B机房的zk节点,如果网络出现抖动会对应用出现较大影响,所以希望部署在A机房的应用连接到A机房的zk节点,且当A机房所有zk节点挂掉后,能够连接B机房zk节点

实现zk节点优先连接思路

查看zk创建连接源码过程分析,创建zookeeper对象


image.png

zookeeper对象的构造函数中,会创建StaticHostProvider对象

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
            long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
        throws IOException
    {
        LOG.info("Initiating client connection, connectString=" + connectString
                + " sessionTimeout=" + sessionTimeout
                + " watcher=" + watcher
                + " sessionId=" + Long.toHexString(sessionId)
                + " sessionPasswd="
                + (sessionPasswd == null ? "<null>" : "<hidden>"));

        watchManager.defaultWatcher = watcher;

        ConnectStringParser connectStringParser = new ConnectStringParser(
                connectString);
        HostProvider hostProvider = new StaticHostProvider(
                connectStringParser.getServerAddresses());
        cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), sessionId, sessionPasswd, canBeReadOnly);
        cnxn.seenRwServerBefore = true; // since user has provided sessionId
        cnxn.start();
    }

进一步查看StaticHostProvider构造函数,发现会调用init方法

/**
     * Constructs a SimpleHostSet.
     *
     * @param serverAddresses
     *            possibly unresolved ZooKeeper server addresses
     * @throws IllegalArgumentException
     *             if serverAddresses is empty or resolves to an empty list
     */
    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
        this.resolver = new Resolver() {
            @Override
            public InetAddress[] getAllByName(String name) throws UnknownHostException {
                return InetAddress.getAllByName(name);
            }
        };
        init(serverAddresses);
    }

    /**
     * Introduced for testing purposes. getAllByName() is a static method of InetAddress, therefore cannot be easily mocked.
     * By abstraction of Resolver interface we can easily inject a mocked implementation in tests.
     *
     * @param serverAddresses
     *            possibly unresolved ZooKeeper server addresses
     * @param resolver
     *            custom resolver implementation
     * @throws IllegalArgumentException
     *             if serverAddresses is empty or resolves to an empty list
     */
    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses, Resolver resolver) {
        this.resolver = resolver;
        init(serverAddresses);
    }

 /**
     * Common init method for all constructors.
     * Resolve all unresolved server addresses, put them in a list and shuffle.
     */
    private void init(Collection<InetSocketAddress> serverAddresses) {
        if (serverAddresses.isEmpty()) {
            throw new IllegalArgumentException(
                    "A HostProvider may not be empty!");
        }

        this.serverAddresses.addAll(serverAddresses);
        Collections.shuffle(this.serverAddresses);
    }

init方法比较简单,就是把传入的zookeeper连接串集合加入serverAddresses集合中,并进行随机打乱
回到zookeeper的构造函数中,创建ClientCnxn对象,并调用start方法

cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
                hostProvider, sessionTimeout, this, watchManager,
                getClientCnxnSocket(), sessionId, sessionPasswd, canBeReadOnly);
        cnxn.seenRwServerBefore = true; // since user has provided sessionId
        cnxn.start();

进一步深入ClientCnxn类start方法,调用了sendThread.start();方法

 public void start() {
        sendThread.start();
        eventThread.start();
    }

进而调用了SendThread的run方法,该方法中存在一个从serverAddresses集合中获取zk连接地址,并创建连接,见方法 hostProvider.next(1000);

 @Override
        public void run() {
            clientCnxnSocket.introduce(this,sessionId);
            clientCnxnSocket.updateNow();
            clientCnxnSocket.updateLastSendAndHeard();
            int to;
            long lastPingRwServer = Time.currentElapsedTime();
            final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
            InetSocketAddress serverAddress = null;
            while (state.isAlive()) {
                try {
                    if (!clientCnxnSocket.isConnected()) {
                        if(!isFirstConnect){
                            try {
                                Thread.sleep(r.nextInt(1000));
                            } catch (InterruptedException e) {
                                LOG.warn("Unexpected exception", e);
                            }
                        }
                        // don't re-establish connection if we are closing
                        if (closing || !state.isAlive()) {
                            break;
                        }
                        if (rwServerAddress != null) {
                            serverAddress = rwServerAddress;
                            rwServerAddress = null;
                        } else {
                            serverAddress = hostProvider.next(1000);
                        }
                        startConnect(serverAddress);
                        clientCnxnSocket.updateLastSendAndHeard();
                    }

这时可看到最终是调用StaticHostProvider类的next方法获取zk连接串,先获取serverAddresses集合第一个元素,如连接失败或超时,再获取第二个元素,以此类推,而serverAddresses集合是在init方法中进行的初始化且进行随机打散

public InetSocketAddress next(long spinDelay) {
        currentIndex = ++currentIndex % serverAddresses.size();
        if (currentIndex == lastIndex && spinDelay > 0) {
            try {
                Thread.sleep(spinDelay);
            } catch (InterruptedException e) {
                LOG.warn("Unexpected exception", e);
            }
        } else if (lastIndex == -1) {
            // We don't want to sleep on the first ever connect attempt.
            lastIndex = 0;
        }

        InetSocketAddress curAddr = serverAddresses.get(currentIndex);
        try {
            String curHostString = getHostString(curAddr);
            List<InetAddress> resolvedAddresses = new ArrayList<InetAddress>(Arrays.asList(this.resolver.getAllByName(curHostString)));
            if (resolvedAddresses.isEmpty()) {
                return curAddr;
            }
            Collections.shuffle(resolvedAddresses);
            return new InetSocketAddress(resolvedAddresses.get(0), curAddr.getPort());
        } catch (UnknownHostException e) {
            return curAddr;
        }
    }

根据以上分析,如要达到zk优先机房连接节点目的,可对StaticHostProvider的init方法进行改造,以优先连接A机房zk节点为例,思路如下:

  • 在zoo.cfg配置文件中增加属性low.priority.connect.ips配置B机房的zk节点
  • 从client传入的连接串中移除low.priority.connect.ips属性配置的ip,并记录到单独集合中
  • 再加入到StaticHostProvider类的serverAddresses集合中
  • 执行Collections.shuffle(this.serverAddresses);
  • 再把移除记录的单独集合追加到StaticHostProvider类的serverAddresses集合末尾

即先把A机房zk节点ip加入到list集合中并进行随机打乱,再把B机房zk节点ip随机打乱追加到list集合中,从而得到A机房zk节点在前,B机房zk节点在后的一个有序集合,从而达到优先连接zk机房zk节点目的,代码如下:

 /**
     * Common init method for all constructors.
     * Resolve all unresolved server addresses, put them in a list and shuffle.
     */
    private void init(Collection<InetSocketAddress> serverAddresses) {
        //初始化observer zk ip
        String zkHome = System.getenv(ZOOKEEPER_HOME);
        Properties properties = new Properties();
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(zkHome + "/conf/zoo.cfg"));
            properties.load(bufferedReader);

            String lowPriorityConnectIPs = properties.getProperty(LOW_PRIORITY_CONNECT_IPS);
            if (lowPriorityConnectIPs != null && lowPriorityConnectIPs != "") {
                String[] split = lowPriorityConnectIPs.split(":");
                observerZks.addAll(Arrays.asList(split));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (serverAddresses.isEmpty()) {
            throw new IllegalArgumentException(
                    "A HostProvider may not be empty!");
        }
        Iterator<InetSocketAddress> iterator = serverAddresses.iterator();
        while (iterator.hasNext()) {
            InetSocketAddress inetSocketAddress = iterator.next();
            String hostString = getHostString(inetSocketAddress);
            String hostIp = null;
            try {
                hostIp = InetAddress.getByName(hostString).getHostAddress();
            } catch (UnknownHostException e) {
                e.printStackTrace();
            }

            if (hostIp != null) {
                boolean contains = observerZks.contains(hostIp);
                if (contains) {
                    observerZkInetSockets.add(inetSocketAddress);
                    iterator.remove();
                }
            }
        }

        this.serverAddresses.addAll(serverAddresses);
        Collections.shuffle(this.serverAddresses);
        Collections.shuffle(this.observerZkInetSockets);
        this.serverAddresses.addAll(observerZkInetSockets);
        if (bufferedReader != null) {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

相关文章

  • 原创-zookeeper集群优先选择连接节点改造

    背景 zookeeper集群跨机房部署,采用observer部署模式,达到目标如下: A机房zk节点参与选举 B机...

  • zookeeper集群角色

    zookeeper的集群 在zookeeper中,客户端会随机连接到zookeeper集群中的一个节点,如果是读请...

  • Zookeeper的集群

    在zookeeper中,客户端会随机连接到zookeeper集群中 的一个节点,如果是读请求,就直接从当前节点中读...

  • docker-compose部署zookeeper&kafka集

    docker-compose部署zookeeper&kafka集群 环境说明 Zookeeper集群 IP节点目录...

  • (6)zookeeper集群

    在 zookeeper 中,客户端会随机连接到 zookeeper 集群中的一个节点,如果是读请求,就直接从当前节...

  • Zookeeper单机安装

    一、zookeeper的特点: 一致性 原子性 单一视图:客户端连接集群中任意一个zookeeper节点,数据都一...

  • zookeeper扩容

    如何扩容zookeeper集群? 场景一,新增加节点大于等于现有集群节点 将待加入节点分成N组,保证每个加入旧集群...

  • zookeeper选举机制

    半数机制集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器。 Zookeeper集群节点...

  • NIFI 系统属性 System Properties之ZooK

    ZooKeeper属性 NiFi依靠Apache ZooKeeper来确定集群中的哪个节点应该扮演主节点的角色,哪...

  • Zookeeper集群的安装

    一、zookeeper集群简介 Zookeeper集群中只要有过半的节点是正常的情况下,那么整个集群对外就是可用的...

网友评论

      本文标题:原创-zookeeper集群优先选择连接节点改造

      本文链接:https://www.haomeiwen.com/subject/evsdtctx.html