一:Apache Curator简介

1. Curator主要从以下几个方面降低了zk使用的复杂性
  1. 重试机制:提供可插拔的重试机制, 它将给捕获所有可恢复的异常配置一个重试策略,并且内部也提供了几种标准的重试策略(比如指数补偿)
  2. 连接状态监控: Curator初始化之后会一直的对zk连接进行监听, 一旦发现连接状态发生变化, 将作出相应的处理
  3. zk客户端实例管理:Curator对zk客户端到server集群连接进行管理.并在需要的情况, 重建zk实例,保证与zk集群的可靠连接
  4. 各种使用场景支持:Curator实现zk支持的大部分使用场景支持(甚至包括zk自身不支持的场景),这些实现都遵循了zk的最佳实践,并考虑了各种极端情况
2. Curator主要解决了三类问题
  1. 封装ZooKeeper client与ZooKeeper server之间的连接处理
  2. 提供了一套Fluent风格的操作API
  3. 提供ZooKeeper各种应用场景(recipe, 比如共享锁服务, 集群领导选举机制)的抽象封装
3. Curator声称的一些亮点
  1. 日志工具
  • 内部采用SLF4J 来输出日志
  • 采用驱动器(driver)机制, 允许扩展和定制日志和跟踪处理
  • 提供了一个TracerDriver接口, 通过实现addTrace()和addCount()接口来集成用户自己的跟踪框架
  1. 和Curator相比, 另一个ZooKeeper客户端——zkClient的不足之处
  • 文档几乎没有
  • 异常处理弱爆了(简单的抛出RuntimeException)
  • 重试处理太难用了
  • 没有提供各种使用场景的实现
  1. 对ZooKeeper自带客户端(ZooKeeper类)的”抱怨”
  • 只是一个底层实现
  • 要用需要自己写大量的代码
  • 很容易误用
  • 需要自己处理连接丢失, 重试等
4. Curator几个组成部分
  1. Client: 是ZooKeeper客户端的一个替代品, 提供了一些底层处理和相关的工具方法
  2. Framework: 用来简化ZooKeeper高级功能的使用, 并增加了一些新的功能, 比如管理到ZooKeeper集群的连接, 重试处理
  3. Recipes: 实现了通用ZooKeeper的recipe, 该组件建立在Framework的基础之上
  4. Utilities:各种ZooKeeper的工具类
  5. Errors: 异常处理, 连接, 恢复等
  6. Extensions: recipe扩展

二:基本示例

1. 基本操作

public class CuratorTest {
    static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
    static final int sessionTimeoutMs = 5000;
    static int connectionTimeoutMs = 3000;

    public static void main(String[] args) throws Exception {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        // namespace: 命名空间,即根节点,当多个应用使用同一个zk时能够避免冲突, 操作时不需要显式使用该根命名空间(根节点)
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(sessionTimeoutMs)
                .connectionTimeoutMs(connectionTimeoutMs)
                .retryPolicy(retryPolicy)
                .namespace("myapp")
                .build();

        // 连接
        client.start();


        String nodePath = "/node1";
        Stat stat = client.checkExists().forPath(nodePath);
        if (stat != null) {
            // 只能删除叶子节点, version=-1 表示不需要校验版本,如果版本不对会抛出异常(BadVersion)
            // version版本相同才会更新
            client.setData().withVersion(-1).forPath(nodePath, "node1 new value".getBytes());

            byte[] bytes = client.getData().forPath(nodePath);
            System.out.println("node1=" + new String(bytes));

            // 读取节点数据,获取该节点的Stat
            Stat stat1 = new Stat();
            client.getData().storingStatIn(stat1).forPath(nodePath);
            System.out.println("stat1=" + stat1);

            client.delete().withVersion(-1).forPath(nodePath);
        } else {
            client.create().forPath(nodePath, "node1 default value".getBytes());
        }

        // creatingParentContainersIfNeeded递归创建节点
        String result = client.create()
                .creatingParentContainersIfNeeded()
                .withMode(CreateMode.PERSISTENT)
                .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                .forPath("/node1/node11/node111", "node111 init value".getBytes());
        System.out.println(result);
        client.setData().forPath("/node1/node11/node111", "node111 new value".getBytes());



        client.create().withMode(CreateMode.PERSISTENT)
                .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                .inBackground()
                .forPath(nodePath + "/node2", "node2 init value".getBytes());

       client.getChildren().forPath(nodePath).forEach(node -> {
            try {
                String fullPath = nodePath + "/" + node;
                System.out.println(node + " = " + new String(client.getData().forPath(fullPath)));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });


        // 删除当前节点和子节点
        // guaranteed是一个保障措施只要客户端会话有效那么Curator会在后台持续进行删除操作,直到删除节点成功。
        client.delete().guaranteed().deletingChildrenIfNeeded().forPath("/node1");
        Thread.sleep(1000);

        // 不存在则创建,存在则更新
        client.create().orSetData().forPath("/node3", "node3 new value".getBytes());

        client.close();
    }
}

2. 事务操作

public class CuratorTest {
    static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
    static final int sessionTimeoutMs = 5000;
    static int connectionTimeoutMs = 3000;

    public static void main(String[] args) throws Exception {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        // namespace: 根节点,操作时不需要显式使用该根命名空间(根节点)
        CuratorFramework client = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(sessionTimeoutMs)
                .connectionTimeoutMs(connectionTimeoutMs)
                .retryPolicy(retryPolicy)
                .namespace("myapp")
                .build();

        // 连接
        client.start();

        // 定义事务操作
        CuratorOp createCuratorOp = client.transactionOp().create().forPath("/node1", "node1 init value".getBytes());
        CuratorOp setCuratorOp = client.transactionOp().setData().forPath("/node1", "node1 new value".getBytes());
        CuratorOp delCuratorOp = client.transactionOp().delete().forPath("/node1");

        // 返回值:为每个操作的结果
        List<CuratorTransactionResult> results = client.transaction().forOperations(createCuratorOp, setCuratorOp, delCuratorOp);
        for (CuratorTransactionResult result: results) {
            System.out.println("执行结果:" + result.getForPath() + "\t" + result.getType() + "\t" + result.getError() + "\t" + result.getResultStat());
        }

        client.close();
    }
}

3.监听

方式一:全局监听

// 只有inBackground()才能被监听到
CuratorListener listener = new CuratorListener() {
    @Override
    public void eventReceived(CuratorFramework client, CuratorEvent event) {
        System.out.println(event.getType() + "\t" + event.getPath());
    }
};
client.getCuratorListenable().addListener(listener);

// inBackground 后台执行,即异步执行
client.create().inBackground().forPath("/node1", "node1 value".getBytes());
client.setData().inBackground().forPath("/node1", "node1 new value".getBytes());
client.delete().inBackground().forPath("/node1");

//        WATCHED   null
//        CREATE    /node1
//        SET_DATA  /node1
//        DELETE    /node1
//        CLOSING   null

方式二:监听单个操作

BackgroundCallback callback = new BackgroundCallback() {
    @Override
    public void processResult(CuratorFramework client, CuratorEvent event) throws Exception {
        System.out.println(event.getType() + "\t" + event.getPath());
    }
};

// inBackground 后台执行,即异步执行
client.create().inBackground(callback).forPath("/node1", "node1 value".getBytes());
client.setData().inBackground(callback).forPath("/node1", "node1 new value".getBytes());
client.delete().inBackground(callback).forPath("/node1");

//        CREATE        /node1
//        SET_DATA      /node1
//        DELETE        /node1

方式三:TreeCache

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.0.1</version>
</dependency>

NodeCache 好像只能监控删除节点和修改节点,没有监控创建节点

final NodeCache cache = new NodeCache(client, "/node1");
NodeCacheListener listener = () -> {
    ChildData data = cache.getCurrentData();
    if (null != data) {
        System.out.println("路径=" + data.getPath() + "\t data=" + new String(data.getData()));
    } else {
        System.out.println("节点被删除!");
    }
};
cache.getListenable().addListener(listener);
cache.start();

client.create().forPath("/node1", "node1 value".getBytes());
client.setData().forPath("/node1", "node1 new value".getBytes());
client.delete().forPath("/node1");

cache.close();

// 路径=/node1     data=node1 new value
// 节点被删除!

PathChildrenCache 好像不能监控修改节点

PathChildrenCache cache = new PathChildrenCache(client, "/node1", true);
cache.start();
PathChildrenCacheListener cacheListener = (client1, event) -> {
    System.out.println("事件类型:" + event.getType());
    if (null != event.getData()) {
        System.out.println("节点数据:" + event.getData().getPath() + " = " + new String(event.getData().getData()));
    }
};
cache.getListenable().addListener(cacheListener);

client.create().creatingParentsIfNeeded().forPath("/node1/node11", "node1 value".getBytes());
client.setData().forPath("/node1/node11", "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/node1");

cache.close();

//        事件类型:CONNECTION_RECONNECTED
//        事件类型:CHILD_ADDED
//        节点数据:/node1/node11 = node1 new value
//        事件类型:CHILD_REMOVED
//        节点数据:/node1/node11 = node1 new value

TreeCache = PathCache + NodeCache 创建、修改、删除都能监控到

TreeCacheListener cacheListener = (client1, event) -> {
    System.out.println("事件类型:" + event.getType() +
            "\t路径:" + (null != event.getData() ? event.getData().getPath() : null));
};
TreeCache cache = new TreeCache(client, "/node1");
cache.start();
cache.getListenable().addListener(cacheListener);

client.create().creatingParentsIfNeeded().forPath("/node1/node11", "node1 value".getBytes());
client.setData().forPath("/node1/node11", "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath("/node1");

Thread.sleep(1000);
cache.close();

//        事件类型:INITIALIZED      路径:null
//        事件类型:NODE_ADDED       路径:/node1
//        事件类型:NODE_ADDED       路径:/node1/node11
//        事件类型:NODE_UPDATED     路径:/node1/node11
//        事件类型:NODE_REMOVED     路径:/node1/node11
//        事件类型:NODE_REMOVED     路径:/node1

ConnectionStateListener
监听连接状态,当连接丢失时去重新连接

public class CuratorTest {
    static final String connectString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
    static final int sessionTimeoutMs = 5000;
    static int connectionTimeoutMs = 3000;
    static CountDownLatch countDownLatch = new CountDownLatch(1);
    static CuratorFramework client;
    static ConnectionStateListener connectionStateListener = new ConnectionStateListener() {
        @Override
        public void stateChanged(CuratorFramework client, ConnectionState newState) {
            if (newState == ConnectionState.CONNECTED) {
                System.out.println("连接成功");
                countDownLatch.countDown();
            } else if (newState == ConnectionState.LOST) {
                System.out.println("连接丢失");

                try {
                    System.out.println("重新初始化开始");
                    reInitClient();
                    System.out.println("重新初始化完毕");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    };

    public static void main(String[] args) throws Exception {
        initClient();
        client.close();
    }

    public static void initClient() throws Exception {
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
        // namespace: 根节点,操作时不需要显式使用该根命名空间(根节点)
        client = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(sessionTimeoutMs)
                .connectionTimeoutMs(connectionTimeoutMs)
                .retryPolicy(retryPolicy)
                .namespace("myapp")
                .build();

        // 连接
        client.start();
        client.getConnectionStateListenable().addListener(connectionStateListener);

        countDownLatch.await();
    }


    public static void reInitClient() throws Exception {
        // 先关闭client
        if (client != null) {
            client.close();
            client = null;
        }

        // 然后再初始化客户端
        initClient();
    }
}

CuratorWatcher 好像只能监听到setData

CuratorWatcher pathWatcher = new CuratorWatcher() {
    @Override
    public void process(WatchedEvent event) throws Exception {
        if (event.getType() == Watcher.Event.EventType.NodeDataChanged) {
            String path = event.getPath();
            String value = new String(client.getData().forPath(path));
            System.out.println(path + "=" +value);
        }

        System.out.println(event);
    }
};

String path = "/node1";
client.create().forPath(path, "node1 value".getBytes());

String value = new String(client.getData().usingWatcher(pathWatcher).forPath(path));
System.out.println(path + "=" + value);

client.create().forPath(path + "/node11", "node1 value".getBytes());
client.setData().forPath(path, "node1 new value".getBytes());
client.delete().deletingChildrenIfNeeded().forPath(path);

Thread.sleep(5000);

// node1=node1 value
// node1=node1 new value
// WatchedEvent state:SyncConnected type:NodeDataChanged path:/node1