二、ZooKeeper 手写分布式配置

一、ZooKeeper 分布式配置实现

ZooKeeper Java Example : https://zookeeper.apache.org/doc/current/javaExample.html

1. 需求

用ZK实现一个配置中心,当节点中的数据变更的时候,应用程序同步最新的配置数据。

2. 代码实现

  1. 添加配置类 Configuration

    @Getter
    @Setter
    @AllArgsConstructor
    public class Config {
    
        //配置文件内容
        private byte[] data;
        // 创建时间
        private long cdate;
        // 修改时间
        private long mdate;
    }
    
  2. 添加配置处理工具类 ConfigUtil

    @Slf4j
    @Getter
    public class ConfigUtil implements Watcher, AsyncCallback.StatCallback, AsyncCallback.DataCallback {
    
        private static volatile ZooKeeper zookeeper;
        private CountDownLatch connectLatch = new CountDownLatch(1);
    
        private volatile Config conf = null;
    
        //本机测试只用了单机
        private final static String ZK_ClUSTER = "localhost:2181/java";
    
        /**
         * 单例模式实现 Zookeeper 客户端连接
         *
         * @return
         */
        public ZooKeeper getZooKeeper() {
            if (zookeeper != null) {
                return zookeeper;
            }
    
            synchronized (ConfigUtil.class) {
                if (zookeeper != null) {
                    return zookeeper;
                }
                try {
                    //在new zk 的时候传入的watch,这个是session级别的,跟node,path没有关系,所以在节点变化的时候是收不到回调的。
                    zookeeper = new ZooKeeper(ZK_ClUSTER, 1000, this);
                    connectLatch.await();
                } catch (IOException e) {
                    log.error("get connection error!", e);
                } catch (InterruptedException e) {
                    log.error("get connection error!", e);
                }
                return zookeeper;
            }
        }
    
        @Override
        public void process(WatchedEvent event) {
            log.info(event.toString());
            switch (event.getType()) {
                case NodeCreated:
                    // 监控到节点创建后,拉取最新信息,并重新watcher
                    this.getZooKeeper().getData(event.getPath(), this, this, null);
                    break;
                case NodeDeleted:
                    conf = null;
                    // 监控到节点删除后,清空数据,并从新watcher 节点
                    readConfig(event.getPath());
                    break;
                case NodeDataChanged:
                    // 监控到节点修改后,拉取最新信息,并重新watcher
                    this.getZooKeeper().getData(event.getPath(), this, this, null);
                    break;
            }
    
            switch (event.getState()) {
                case SyncConnected:
                    //通过 CountDownLatch 控制 zookeeper 客户端的初使链接
                    connectLatch.countDown();
                    break;
            }
        }
    
        /**
         * This callback is used to retrieve the stat of the node
         *
         * @param rc   The return code or the result of the call.
         * @param path The path that we passed to asynchronous calls.
         * @param ctx  Whatever context object that we passed to asynchronous calls.
         * @param stat {@link Stat} object of the node on given path.
         * @see ZooKeeper#exists(String, boolean, StatCallback, Object)
         * @see ZooKeeper#exists(String, Watcher, StatCallback, Object)
         * @see ZooKeeper#setData(String, byte[], int, StatCallback, Object)
         * @see ZooKeeper#setACL(String, List, int, StatCallback, Object)
         */
        @Override
        public void processResult(int rc, String path, Object ctx, Stat stat) {
            log.info("DataCallback event: rc={}, path={}, ctx={}, stat={}", rc, path, ctx, stat);
            if (KeeperException.Code.OK.intValue() == rc) {
                // 节点存在,拉取数据
                this.getZooKeeper().getData(path, this, this, null);
            }
        }
    
        /**
         * This callback is used to retrieve the data and stat of the node
         *
         * @param rc   The return code or the result of the call.
         * @param path The path that we passed to asynchronous calls.
         * @param ctx  Whatever context object that we passed to asynchronous calls.
         * @param data The data of the node.
         * @param stat {@link Stat} object of the node on given path.
         * @see ZooKeeper#getData(String, boolean, DataCallback, Object)
         * @see ZooKeeper#getData(String, Watcher, DataCallback, Object)
         * @see ZooKeeper#getConfig(boolean, DataCallback, Object)
         * @see ZooKeeper#getConfig(Watcher, DataCallback, Object)
         */
        @Override
        public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
            log.info("StatCallback event: rc={}, path={}, ctx={}, data={}, stat={}", rc, path, ctx, new String(data), stat);
            if (rc == KeeperException.Code.OK.intValue()) {
                conf = new Config(data, stat.getCtime(), stat.getMtime());
            }
        }
    
        /**
         * 先判断是否存在并注入watcher事件和callback
         * 这里要用exists, 因为当节不存在的时候,getData 的watcher监控不到创建节点的事件。
         */
        public void readConfig(String filepath) {
            this.getZooKeeper().exists(filepath, this, this, null);
    //        this.getZooKeeper().getData(filepath, this, this, null);
        }
    }
    
  3. 添加测试类

    该类主要是一个死循环,不断的去读取最新的配置内容并打印。当读取到 stop 的时候直接退出测试。

    同时可以用另一个客户端,对节点做修改等操作。

    @Slf4j
    public class ConfigTest {
    
        public static void main(String[] args) {
            ConfigUtil configUtil = new ConfigUtil();
          	// 通过 AutoCloseable 自动关闭资源
            try (ZooKeeper zooKeeper = configUtil.getZooKeeper()) {
                configUtil.readConfig("/app/conf");
                for (; ; ) {
                    if (configUtil.getConf() != null) {
                        String data = new String(configUtil.getConf().getData());
                        log.info("conf: {}", data);
                        if (data.equalsIgnoreCase("stop")) {
                            log.info("exiting....");
                            break;
                        }
                    } else {
                        log.info("has no conf....");
                    }
                    TimeUnit.SECONDS.sleep(3);
                }
                log.info("done !!!");
            } catch (InterruptedException e) {
                log.error("auto close error !!!", e);
            } finally {
                log.info("finish test!!!");
            }
    
        }
    }
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值