Curator - 初步简单使用和认识

207 阅读4分钟

这个框架 , 相对于Zk提供官方客户端, 功能更多一些. 也很实用. 其实就是封装了一下.

这一章节 , 基本上就是介绍这个API的简单的使用.下一章节, 主要是介绍 它的分布式锁 , 选举机制的使用.

1. 简单使用一下API.

启动很简单. 直接这么编程就可以了. 可以加入几个启动的监听器.

创建客户端

private static CuratorFramework getCuratorFramework() {
    RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
    CuratorFramework client = CuratorFrameworkFactory.newClient("192.168.58.131:2181", retryPolicy);
    client.getConnectionStateListenable().addListener((c, newState) -> System.out.println("connect : " + c));
    //启动
    client.start();
    return client;
}

实用builder 创建客户端.

// 指数. 增长
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
        // 这个意思其实是初始化的时候创建一个父节点.记住不需要加 / ,该客户端所有的操作都基于该空间上 CRUD.
        .namespace("node")
        .connectionTimeoutMs(1000)
        .connectString("192.168.58.131:2181")
        .retryPolicy(retryPolicy)
        .build();

// 启动
client.start();

创建一个节点.

public static void main(String[] args) throws Exception {
    // 启动zk客户端
    CuratorFramework client = Zk.getCuratorFramework();

    // 路径
    String path = "/create-data/eph_";
    // 创建一个节点. 临时自增节点. 完全开放的策略. 数据为init
    client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE).forPath(path, "init".getBytes());
    // 获取子节点.
    List<String> list = client.getChildren().forPath("/create-data");
    list.forEach(System.out::println); //eph_0000000000
}

​ zk 3.5 后面加入了一个容器节点. 当容器类型的节点下的子节点全部被删除时,则该节点也会在一段时间内被删除。

判断一个节点是否存在.

监测是节点是否存在 :

private static void checkExist(String path) throws Exception {
    CuratorFramework client = getCuratorFramework();
    // 调用 checkExists() 方法. 
    Stat stat = client.checkExists().forPath(path);
    if (stat == null) {
        System.out.println("不存在");
    } else {
        System.out.println("存在 : " + stat);
    }
    client.close();
}

获取节点数据 :

private static void getData(String path) throws Exception {
    CuratorFramework client = getCuratorFramework();
    byte[] bytes = client.getData().forPath(path);
    System.out.println("data : " + new String(bytes));
}

获取子节点

List<String> list = client.getChildren().forPath("/test2");

添加watcher

public static void main(String[] args) throws Exception {
    // 启动zk客户端
    CuratorFramework client = Zk.getCuratorFramework();
    // 路径
    String path = "/create-data-exit";

    // 添加监听器.
    client.getData().usingWatcher(new Watcher() {
        @Override
        public void process(WatchedEvent event) {
            System.out.println(event.getType());
        }
    }).forPath(path);

    client.setData().forPath(path, "change".getBytes());
}

2. 重复监听

​ 一下三种对于并发写入都是不支持 , 全部都可以监听到的.

我们自己写一个 重复监听. 其实就是一个递归式

public class App4 {

    public static void main(String[] args) throws Exception {
        // 启动zk客户端
        CuratorFramework client = Zk.getCuratorFramework();

        // 先删除
        String path = "/test-watcher";
        client.delete().deletingChildrenIfNeeded().forPath(path);
        // 创建当前节点
        client.create().withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

        // 重复监听
        watch(client, path);
        
        client.setData().forPath(path, "change-1".getBytes());
        client.setData().forPath(path, "change-2".getBytes());
        System.in.read();
    }

    private static void watch(CuratorFramework client, String path) throws Exception {
        client.getData().usingWatcher(new CuratorWatcher() {
            @Override
            public void process(WatchedEvent event) throws Exception {
                System.out.println(event.getPath() + " : " + event.getType());
                // 递归重复监听.
                watch(client, path);
            }
        }).forPath(path);
    }
}

NodeCache 单节点监听.

可以帮助我们做一些事情. 就是重复监听.

​ A utility that attempts to keep the data from a node locally cached. This class will watch the node, respond to update/create/delete events, pull down the data, etc. You can register a listener that will get notified when changes occur.

public class App {

    public static void main(String[] args) throws Exception {
        // 启动zk客户端
        CuratorFramework client = Zk.getCuratorFramework();

        // 先删除
        String path = "/test-watcher";
        client.delete().forPath(path);
        // 创建当前节点
        client.create().withMode(CreateMode.PERSISTENT).forPath(path, "hello world - init".getBytes());

        // 创建一个 NodeCache
        NodeCache cache = new NodeCache(client, path);

        // true : 会保存初始化信息. 原状态信息 , false不会
        cache.start(true);
        // 所以这里可以直接拿到初始化信息
        System.out.println("初始化信息 :" + new String(cache.getCurrentData().getData()));

        // 添加listener.
        cache.getListenable().addListener(new NodeCacheListener() {
            @Override
            public void nodeChanged() throws Exception {
                System.out.println("当前节点发生改变:" + new String(cache.getCurrentData().getData()));
            }
        });

        ExecutorService pool = Executors.newFixedThreadPool(5);

        IntStream.range(0, 10).forEach(value -> pool.execute(() -> {
            try {
                // 改变值. 并发改变
                client.setData().forPath(path, String.format("change-%d", value).getBytes());
            } catch (Exception e) {
                //
            }
        }));
    }
}

结果是什么?

当前节点发生改变:hello world - init
当前节点发生改变:change-9

是不是很吃惊. 因为这个回调本身没有做同步机制. 所以对于高的写入修改可能做不到及时的监听. 也正常.

PathChildrenCache

​ 只监听改目录下的第一级子节点.

public class App3 {

    public static void main(String[] args) throws Exception {
        // 启动zk客户端
        CuratorFramework client = Zk.getCuratorFramework();

        // 先删除
        String path = "/test-watcher";
        client.delete().deletingChildrenIfNeeded().forPath(path);
        // 创建当前节点
        client.create().withMode(CreateMode.PERSISTENT).forPath(path, "hello world - 1".getBytes());

        // 创建一个 PathChildrenCache
        PathChildrenCache childrenCache = new PathChildrenCache(client, path, true);

        /**
         * NORMAL:正常模式
         * BUILD_INTIAL_CACHE:启动什么事件都不输出
         * POST_INITAL_CACHE:启动会输出初始化事件
         */
        childrenCache.start(PathChildrenCache.StartMode.NORMAL);

        // 添加listener.
        childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
            @Override
            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
                System.out.println(String.format("path: %s, type: %s,value: %s.", event.getData().getPath(), event.getType(), new String(event.getData().getData())));

            }
        });
        ExecutorService pool = Executors.newFixedThreadPool(5);
        IntStream.range(0, 10).forEach(value -> pool.execute(() -> {
            try {
                String path_c = client.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).forPath(path  + path + "_child_" + value, "init".getBytes());
                client.setData().forPath(path_c, String.format("change-%d", value).getBytes());
            } catch (Exception e) {
                System.out.println(e.getMessage());
            }
        }));
    }
}

输出 : 我发现它不能监听子节点的创建.

path: /test-watcher/test-watcher_child_4, type: CHILD_ADDED,value: init.
path: /test-watcher/test-watcher_child_3, type: CHILD_ADDED,value: init.
path: /test-watcher/test-watcher_child_2, type: CHILD_ADDED,value: init.
path: /test-watcher/test-watcher_child_1, type: CHILD_ADDED,value: init.
path: /test-watcher/test-watcher_child_0, type: CHILD_ADDED,value: init.
path: /test-watcher/test-watcher_child_1, type: CHILD_UPDATED,value: change-1.
path: /test-watcher/test-watcher_child_2, type: CHILD_UPDATED,value: change-2.
path: /test-watcher/test-watcher_child_0, type: CHILD_UPDATED,value: change-0.
path: /test-watcher/test-watcher_child_4, type: CHILD_UPDATED,value: change-4.
path: /test-watcher/test-watcher_child_3, type: CHILD_UPDATED,value: change-3.
path: /test-watcher/test-watcher_child_8, type: CHILD_ADDED,value: change-8.
path: /test-watcher/test-watcher_child_7, type: CHILD_ADDED,value: change-7.
path: /test-watcher/test-watcher_child_6, type: CHILD_ADDED,value: change-6.
path: /test-watcher/test-watcher_child_5, type: CHILD_ADDED,value: change-5.
path: /test-watcher/test-watcher_child_9, type: CHILD_ADDED,value: change-9.

反正我感觉监听不准确. 并且还有点乱. 无非是因为 zk 是一个 写少读多的场景.

TreeCache

​ 这个很简单其实就是 , 子级目录也监听.

public static void main(String[] args) throws Exception {
    // 启动zk客户端
    CuratorFramework client = Zk.getCuratorFramework();

    // 先删除
    String path = "/test-watcher";
    client.delete().deletingChildrenIfNeeded().forPath(path);
    // 创建当前节点
    client.create().withMode(CreateMode.PERSISTENT).forPath(path, "init".getBytes());

    TreeCache cache = new TreeCache(client, path);
    cache.getListenable().addListener(new TreeCacheListener() {
        @Override
        public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {

            // todo
        }
    });
}

基本写法一样.