zookeeper 常见客户端介绍和使用 zkCli、自带API、 zkClient、Curator
作者:mmseoamin日期:2024-02-04

文章目录

  • 一、Zookeeper的命令行使用
  • 二、Zookeeper自带API的使用
    • 2.1 引入API
    • 2.1 API简单使用
    • 三、Zookeeper三方客户端zkClient的使用
      • 3.1 引入依赖
      • 3.2 简单的使用案例
      • 四、Curator 客户端框架
      • 4.1 引入依赖
      • 4.2 简单使用案例

        一、Zookeeper的命令行使用

        ZooKeeper解压后,在其bin目录下包含着常用的程序,例如 zkServer.sh zkCli.sh

        我们使用zkCli.sh 就可以通过命令行使用Zookeeper客户端

        连接zookeeper服务器

        连接后输入help就可以查看所有命令和使用方式的说明了

        #对于本地默认端口 则可以直接 ./zkCli.sh 
        # -server 指定服务地址和端口
        [root@localhost bin]# ./zkCli.sh -server localhost:15881
        

        创建节点命令

        create [-s][-e] path data acl

        -s或-e分别指定节点特性,顺序或临时节点,若不指定,则创建持久节点;acl⽤来进⾏权限控制。

        # 创建顺序节点
        [zk: localhost:15881(CONNECTED) 0] create -s /zk-test dataContent1111
        Created /zk-test0000000007 
        # 创建临时节点,临时节点在会话结束后由就会被自动删除
        [zk: localhost:15881(CONNECTED) 0] create -e /zk-temp data222
        Created /zk-temp
        # 创建永久节点
        [zk: localhost:15881(CONNECTED) 2] create /zk-test-permanent data333
        Created /zk-test-permanent
        

        读取节点

        可以使用ls查看子节点列表,使用 get 命令查看节点的内容

        # 使用 ls 命令查看子节点
        [zk: localhost:15881(CONNECTED) 4] ls /
        [lg-PERSISTENT, zk-premament, zk-temp, zk-test-permanent, zk-test0000000000, zk-test0000000007, zookeeper]
        # 使用 get 命令查看节点内容 get -s 则可以附加打印节点状态信息
        [zk: localhost:15881(CONNECTED) 6] get /zk-temp
        data222
        # stat 命令查看节点状态
        [zk: localhost:15881(CONNECTED) 0] stat /zk-temp
        cZxid = 0x30000000a
        ctime = Wed Jul 05 10:48:44 CST 2023
        mZxid = 0x30000000a
        mtime = Wed Jul 05 10:48:44 CST 2023
        pZxid = 0x30000000a
        cversion = 0
        dataVersion = 0
        aclVersion = 0
        ephemeralOwner = 0x100008d52290003
        dataLength = 7
        numChildren = 0
        

        更新节点内容

        命令:set path data [version] version表示数据版本,在zookeeper中,节点的数据是有版本概念的,这个参数⽤于指定本次更新操作是基于Znode的哪⼀个数据版本进⾏的,如果版本和最新版本对不上则会更新失败,这样可以防止覆盖最新写入的数据。

        set /zk-premament 666
        

        删除节点

        删除命令 **delete path [version]**** **如果删除的节点包含子节点,那么必须先删除子节点才能删除对应节点。

        二、Zookeeper自带API的使用

        2.1 引入API

        通过Maven引入Zookeeper提供了java客户端API依赖,截至当前时间最新稳定版是 3.7.1

        
          org.apache.zookeeper
          zookeeper
          3.7.1
        
        

        2.1 API简单使用

        /**
         * zookeeper API 简单使用
         *
         * @author liuyp
         */
        public class ZookeeperApiSimpleTest {
            //是否完成连接的建立
            static boolean connected = false;
            static Object lock = new Object();
            //zookeeper实例对象
            static ZooKeeper zooKeeper;
            //定义Watcher的回调 它会收到客户端状态变化的通知,也可以收到节点事件的通知
            static Watcher watcherProcess = (watchedEvent) -> {
                //客户端连接成功状态通知
                if (watchedEvent.getState() == Watcher.Event.KeeperState.SyncConnected && !connected) {
                    System.out.println("watcher回调:客户端连接上线");
                    synchronized (lock) {
                        //连接成功就通知方法返回
                        connected = true;
                        lock.notifyAll();
                    }
                }
                //子节点列表变化通知
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeChildrenChanged) {
                    try {
                        //获取最新的子节点,并重新开启watch
                        List children = zooKeeper.getChildren(watchedEvent.getPath(), true);
                        System.out.println("watcher回调:子节点变化通知 节点:" + watchedEvent.getPath() + " 的最新子节点:" + children);
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //节点内容变更事件
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeDataChanged) {
                    try {
                        byte[] data = zooKeeper.getData(watchedEvent.getPath(), false, null);
                        System.out.println("watcher回调:节点数据变化通知 节点:" + watchedEvent.getPath() + " 内容为:" + new String(data));
                    } catch (KeeperException e) {
                        e.printStackTrace();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //节点删除通知
                if (watchedEvent.getType() == Watcher.Event.EventType.NodeDeleted) {
                    System.out.println("watcher回调:节点被删除通知:" + watchedEvent.getPath());
                }
            };
            /**
             * demo测试入口
             *
             * @param args
             * @throws IOException
             * @throws InterruptedException
             * @throws KeeperException
             */
            public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
                //同步的方式建立会话
                createSession();
                //测试创建节点,先删除上一次创建的
                createZNode();
                //获取节点数据
                getZNodeData();
                //更新节点数据
                updateZNodeData();
                //删除节点
                deleteZNode();
            }
            /**
             * 一、创建会话
             * 创建Zookeeper会话初始化Zookeeper对象
             * 这里改成同步执行,连接上了方法才返回
             */
            public synchronized static void createSession() throws IOException, InterruptedException {
                //可以配置多个地址客户端会随机连接例如 192.168.188.130:15881,192.168.188.130:15882
                String connectString = "192.168.188.130:15881";
                //会话超时时间 单位是毫秒
                int sessionTimeout = 5000;
                //执行结果立即返回,后台异步建立连接。watcherProcess
                zooKeeper = new ZooKeeper(connectString, sessionTimeout, watcherProcess);
                if (connected) {
                    return;
                }
                //如果没执行完,就让出锁进入等待状态,等待出结果后被唤醒
                synchronized (lock) {
                    lock.wait();
                }
            }
            /**
             * 二、创建znode
             */
            public static void createZNode() throws KeeperException, InterruptedException {
                //创建一个测试的公共节点,后续都在这个节点下面测试,并且给他加一个watch
                String testParentNodePath = "/zookeeperApi";
                if (zooKeeper.exists(testParentNodePath,false)==null){
                    zooKeeper.create(testParentNodePath, "父节点".getBytes(StandardCharsets.UTF_8), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                }
                //添加监听 exist&getData
                zooKeeper.addWatch(testParentNodePath, AddWatchMode.PERSISTENT_RECURSIVE);
                zooKeeper.getChildren(testParentNodePath, true);
                /**
                 * path:节点创建路径
                 * data[] :字节数组格式保存到节点的数据
                 * acl:节点ACL权限设置
                 * createMode:创建的节点类型。PERSISTENT:持久节点 EPHEMERAL临时节点 ,还有临时顺序节点,持久顺序节点
                 */
                String zNodePersistent = zooKeeper.create(
                        testParentNodePath + "/persistent",
                        "持久节点内容".getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                String zNodeEphemeralSequential = zooKeeper.create(
                        testParentNodePath + "/ephemeralSequential",
                        "临时顺序节点内容".getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
                String zNodeEphemeral = zooKeeper.create(
                        testParentNodePath + "/persistentEphemeral",
                        "临时节点内容".getBytes(),
                        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
            }
            /**
             * 三、获取节点数据
             */
            public static void getZNodeData() throws KeeperException, InterruptedException {
                String testParentNodePath = "/zookeeperApi";
                byte[] data = zooKeeper.getData(testParentNodePath, false, null);
                System.out.println("节点:" + testParentNodePath + " 内容为:" + new String(data));
            }
            /**
             * 三、更新节点数据
             */
            public static void updateZNodeData() throws KeeperException, InterruptedException {
                String testParentNodePath = "/zookeeperApi";
                zooKeeper.setData(testParentNodePath, ("新数据" + Math.random()).getBytes(), -1);
            }
            /**
             * 四、删除znode
             */
            public static void deleteZNode() throws KeeperException, InterruptedException {
                String testParentNodePath = "/zookeeperApi";
                zooKeeper.delete(testParentNodePath + "/persistent", -1);
            }
        }
        

        三、Zookeeper三方客户端zkClient的使用

        项目地址:https://github.com/sgroschupf/zkclient/issues

        zkClient是git上的一个开源的zookeeper的java客户端项目,是对zookeeper原生API的封装,使得其更易用了。

        优势:1. session重连 2.watch重主策 3.递归删除/添加节点

        注意:项目最新更新日期是2018年,上生产使用前需要考虑漏洞问题。

        3.1 引入依赖

        
        
          com.101tec
          zkclient
          0.11
        
        

        3.2 简单的使用案例

        public class ZkClientTest {
            static CountDownLatch countDownLatch = new CountDownLatch(1);
            public static void main(String[] args) throws InterruptedException {
                String testzkClientPath = "/zkClientAPI";
                //建立连接,这里是同步的方式
                String connectString = "192.168.188.130:15881";
                ZkClient zkClient = new ZkClient(connectString);
                //创建节点,zkClient支持递归创建,没有父节点会自动创建对应的父节点
                zkClient.createPersistent(testzkClientPath + "/persistent", true);
                zkClient.createPersistent(testzkClientPath + "/persistent_readyDelete", true);
                //删除节点 zkClient支持自动删除节点下的子节点
                zkClient.delete(testzkClientPath + "/persistent_readyDelete", -1);
                //获取子节点
                List children = zkClient.getChildren(testzkClientPath);
                System.out.println("读取节点:" + testzkClientPath + " 子节点:" + children);
                //监听事件注册
                //注册子节点变更事件
                zkClient.subscribeChildChanges(testzkClientPath, (path, childNodeList) -> {
                    System.out.println("节点子节点监听事件通知:节点:" + path + " 最新子节点:" + childNodeList);
                });
                //注册节点数据变更事件
                zkClient.subscribeDataChanges(testzkClientPath, new IZkDataListener() {
                    @Override
                    public void handleDataChange(String s, Object o) throws Exception {
                        System.out.println("节点数据监听事件通知:节点:" + s + " 最新数据:" + o);
                    }
                    @Override
                    public void handleDataDeleted(String s) throws Exception {
                        System.out.println("节点数据监听事件通知:节点:" + s + " 已删除");
                    }
                });
                //写入节点数据
                zkClient.writeData(testzkClientPath, System.currentTimeMillis() + "写入数据");
                //获取节点数据
                Object readDataResult = zkClient.readData(testzkClientPath);
                System.out.println("读取节点数据:" + testzkClientPath + " : " + readDataResult);
                //删除节点
                zkClient.deleteRecursive(testzkClientPath);
                //阻塞最后的结束程序
                countDownLatch.await();
            }
        }
        

        四、Curator 客户端框架

        项目地址:https://github.com/apache/curator

        最开始由 netflix 在github上开源,2013年成为apache顶级项目,至今仍在更新

        和ZkClient一样,Curator解决了很多细节的底层工作,包括连接重连、watch自动重新注册

        节点不存在异常等,并且提供了基于fluent编程风格的支持

        4.1 引入依赖

        
        
          org.apache.curator
          curator-framework
          5.5.0
        
        

        4.2 简单使用案例

        /**
         * Curator 是Netflix公司开源的一套ZooKeeper客户端框架
         * 和ZkClient一样,Curator解决了很多细节的底层工作,包括连接重连、watch自动重新注册
         * 节点不存在异常等,并且提供了基于fluent编程风格的支持
         * @author liuyp
         */
        public class CuratorTest {
            public static void main(String[] args) throws Exception {
                //连接信息,多个连接使用逗号分隔
                String connectString = "192.168.188.130:15881";
                /**
                 * 一、发起连接
                 *
                 * RetryPolicy重连策略 默认提供三种重连策略
                 * 1、ExponentialBackoffRetry(基于backoff的重连策略)重新尝试一定次数,并增加重试之间的睡眠时间
                 * 2、RetryNTimes(重连N次策略)
                 * 3、RetryForever(永远重试策略)
                 *
                 * 创建连接 CuratorFramework
                 * 1、通过CuratorFrameworkFactory.newClient 底层是CuratorFrameworkFactory.build
                 * 2、直接通过 CuratorFrameworkFactory.build
                 *
                 * 启动连接 CuratorFramework.start()
                 */
                int baseSleepTimeMs=1000; //重试之间等待的初始时间
                int maxRetries=5;//最大重试次数
                int maxSleepMs=5000;//每次重试的最大睡眠时间 如果算出来的sleepMs超过这个时间,则采用maxSleepMs
                //重试间隔时间: baseSleepTimeMs * Math.max(1, random.nextInt(1 << (retryCount + 1)));
                RetryPolicy retryPolicy=new ExponentialBackoffRetry(baseSleepTimeMs,maxRetries,maxSleepMs);
                CuratorFramework client = CuratorFrameworkFactory.builder()
                        .connectString(connectString)
                        .sessionTimeoutMs(10000)
                        .connectionTimeoutMs(5000)
                        .retryPolicy(retryPolicy)
                        .namespace("curatorAPI") //加上这个以后,所有路径都是以这个路径为根路径
                        .build();
                client.start();
                System.out.println("**********客户端已启动**********");
                /**
                 * 二、创建节点
                 * 1、默认创建内容为空的永久节点
                 * 2、设置节点内容和原生一样,使用字节数组
                 * 3、可以使用 creatingParentsIfNeeded 方法自动创建父节点,避免需要递归判断父节点是否存在
                 */
                client.create()
                        .creatingParentContainersIfNeeded()
                        .withMode(CreateMode.PERSISTENT)
                        .forPath("/tempNode/create","临时节点".getBytes(StandardCharsets.UTF_8));
                /**
                 * 三、测试增加监听
                 * 1、监听类型 PERSISTENT_RECURSIVE 会循环监听注册节点和其子节点的数据变化和是否存在
                 */
                CuratorWatcher curatorWatcher=(watchevent)->{
                    System.out.println("[监听通知:]"+"节点:"+watchevent.getPath()+" "+watchevent.getType());
                };
                client.watchers().add().withMode(AddWatchMode.PERSISTENT_RECURSIVE).usingWatcher(curatorWatcher).forPath("/tempNode");
                client.create().forPath("/tempNode/watcher");
                /**
                 * 三、读取&修改节点数据 并获取状态数据
                 */
                Stat stat=new Stat();
                byte[] bytes = client.getData().storingStatIn(stat).forPath("/tempNode/create");
                System.out.println("读取节点数据:"+new String(bytes,StandardCharsets.UTF_8));
                System.out.println("读取节点状态:"+stat.toString());
                client.setData().forPath("/tempNode/create","节点/tempNode/create的新数据".getBytes(StandardCharsets.UTF_8));
                /**
                 * 四、删除节点
                 */
                client.delete().forPath("/tempNode/watcher");
                client.delete().forPath("/tempNode/create");
                client.delete().forPath("/tempNode");
            }
        }