Zookeeper Java 客户端框架

  • Zookeeper 原生客户端
    zookeeper 官方提供的 java 客户端 API。
  • ZkClient
    开源的 zk 客户端,在原生 API 基础上封装,是一个更易于使用的 zookeeper 客户端。
  • Curator
    开源的 zk 客户端,在原生 API 基础上封装,apache 顶级项目。

推荐使用 Curator,支持 lambda 表达式,链式操作,还有事务管理,且封装了常用的功能。

Zookeeper 原生客户端

maven pom 依赖:

<dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <version>3.6.2</version>
</dependency>
// 创建会话
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, long sessionId, byte[] sessionPasswd, boolean canBeReadOnly)
// 创建节点
public String void create(final String path, byte data[], List<ACL> acl, CreateMode createMode,  StringCallback cb, Object ctx)
// 读取数据
public List<String> void getChildren(final String path, Watcher watcher, Stat stat, Children2Callback cb, Object ctx)
public List<String> void getData(final String path, Watcher watcher, Stat stat, DataCallback cb, Object ctx)
// 更新数据
public Stat void setData(final String path, byte data[], int version, StatCallback cb, Object ctx)
// 检测节点是否存在
public Stat void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)
// 权限控制
public void addAuthInfo(String scheme, byte auth[])
// watch
org.apache.zookeeper.Watcher

增删改查 - CRUD

@Slf4j
public class ZKDemo {
    private final static String CONNECTIONS_TR = "192.168.132.129:2181";

    public void crud() {
        // 等待 ZooKeeper 连接完毕,再进行操作,所以使用 CountDownLatch 进行控制。
        CountDownLatch countDownLatch = new CountDownLatch(1);
        // new ZooKeeper(连接,超时时间,Watcher),watcher 相当于触发器。
        try (ZooKeeper zooKeeper = new ZooKeeper(CONNECTIONS_TR, 500, event -> {
            // ZooKeeper 连接完毕,countDownLatch 放行。
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                countDownLatch.countDown();
            }
            if (event.getType() == Watcher.Event.EventType.NodeDataChanged) {
                log.debug("节点发生了变化,路径:" + event.getPath());
            }
        })) {
            // 等待 ZooKeeper 连接完毕
            countDownLatch.await();
            log.debug("当前连接状态是:" + zooKeeper.getState().toString());
            // 创建一个节点。create(节点路径, 节点值, ACL 权限, 节点类型)
            zooKeeper.create("/node_java_1", "value".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);

            // 获取数据。getData(节点路径, Watcher, Stat)
            byte[] data = zooKeeper.getData("/node_java_1", true, new Stat());
            log.debug("节点的值:" + new String(data));

            // 修改数据。setData(节点路径, 节点值, 版本控制),-1 表示不做版本控制
            // 修改之后会触发 watcher,因为上面 getData 中的 watch 参数为 true
            // 注意这个 watch 是一次性的,如果进行第二次 setData,就不会触发 watcher 了,需要在 setData 之前再写一遍 getData,并让 watch 参数为 true
            zooKeeper.setData("/node_java_1", "value2".getBytes(), -1);
            // 不会触发 watcher 了
            zooKeeper.setData("/node_java_1", "value3".getBytes(), -1);
            // 这样才能触发 watcher
            zooKeeper.getData("/node_java_1", true, new Stat());
            zooKeeper.setData("/node_java_1", "value4".getBytes(), -1);

            // 获取子节点。getChildren(节点路径, Watcher)
            List<String> children = zooKeeper.getChildren("/node_java_1", true);
            log.debug("/node_java_1 的子节点:" + children);

            // 删除节点。delete(节点路径, 版本控制),-1 表示不做版本控制
            zooKeeper.delete("/node_java_1", -1);
        } catch (InterruptedException | IOException | KeeperException e) {
            e.printStackTrace();
        }
    }
}

ACL

@Slf4j
public class ZKDemo {
    private final static String CONNECTIONS_TR = "192.168.132.129:2181";

    public void acl() {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try (ZooKeeper zooKeeper = new ZooKeeper(CONNECTIONS_TR, 500, event -> {
            if (event.getState() == Watcher.Event.KeeperState.SyncConnected) {
                countDownLatch.countDown();
                log.debug("连接完毕:" + event.getState() + " --> " + event.getType());
            }
        })) {
            countDownLatch.await();
            // 创建 ACL
            ACL acl_all_digest = new ACL(ZooDefs.Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("root:root")));
            ACL acl_create_ip = new ACL(ZooDefs.Perms.CREATE, new Id("ip", "192.168.1.1"));
            List<ACL> acl = Arrays.asList(acl_all_digest, acl_create_ip);
            zooKeeper.create("/auth1", "123".getBytes(), acl, CreateMode.PERSISTENT);
            // 如果是另一个连接,需要重新执行这句话。因为这个权限是会话级的。
            zooKeeper.addAuthInfo("digest", "root:root".getBytes());
            zooKeeper.create("/auth1/auth1-1", "123".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL, CreateMode.EPHEMERAL);
        } catch (IOException | InterruptedException | NoSuchAlgorithmException | KeeperException e) {
            e.printStackTrace();
        }
    }
}

弊端

  • 会话的连接是异步的
  • Watch 需要重复注册
  • Session 重连机制
    如果不用 CountDownLatch 的话,有时能连上,有时连不上。
  • 开发复杂性较高

ZkClient

<dependency>
      <groupId>com.101tec</groupId>
      <artifactId>zkclient</artifactId>
      <version>0.11</version>
</dependency>
// 创建会话(同步,重试)
public ZkClient(final String zkServers, final int sessionTimeout, 
                final int connectionTimeout, final ZkSerializer zkSerializer, 
                final long operationRetryTimeout)
// 创建节点(同步,递归创建)
public String create(String path,Object data,final List<ACL> acl,CreateMode mode)
public void createPersistent(String path,boolean createParents,List<ACL> acl)
public void createPersistent(String path, Object data, List<ACL> acl)
public String createPersistentSequential(String path,Object data,List<ACL> acl)
public void createEphemeral(String path, Object data, List<ACL> acl)
public String createEphemeralSequential(String path,Object data,List<ACL> acl)
// 删除节点(同步,递归删除)
public boolean delete(String path,int version)
public boolean deleteRecursive(String path)
// 获取节点(同步,避免不存在异常)
public List<String> getChildren(String path)
public <T> T readData(String path, boolean returnNullIfPathNotExists)
public <T> T readData(String path, Stat stat) 
// 更新节点(同步,实现CAS,状态返回)
public void writeData(String path, Object datat, int expectedVersion)
public Stat writeDataReturnStat(String path,Object datat,int expectedVersion)
// 检测节点存在(同步)
public boolean exists(String path) 
// 权限控制(同步)
public void addAuthInfo(String scheme, final byte[] auth);
public void setAcl(final String path, final List<ACL> acl);
// 监听器
IZkStateListener     (un)subscribeStateChanges(IZkStateListener listener)
IZkDataListener      (un)subscribeDataChanges(IZkStateListener listener)
IZkChildListener     (un)subscribeChildChanges(IZkStateListener listener)

增删改查

@Slf4j
public class ZKClientDemo {
      // 逗号分隔,链接集群。
    private final static String CONNECTIONS_TR = "192.168.199.128:2181";

    public void crud() {
        // 同步连接,如果显示超时,可以把超时时间调大再试
        ZkClient zkClient = new ZkClient(CONNECTIONS_TR, 60000);
        log.debug(zkClient + " -> success.");

        // 级联创建节点
        zkClient.createPersistent("/node-1/node-1-1", true);

        // 获取子节点
        List<String> list = zkClient.getChildren("/node-1");
        log.debug("/node-1 的子节点:" + list);

        // Watcher 监听不是一次性的
        // /node-1 节点数据内容(value)变化触发
        zkClient.subscribeDataChanges("/node-1", new IZkDataListener() {
            @Override
            public void handleDataChange(String s, Object o) throws Exception {
                log.debug("节点名称:" + s + "->节点修改后的值" + o);
            }

            @Override
            public void handleDataDeleted(String s) throws Exception {

            }
        });
        zkClient.writeData("/node-1", "node");
        zkClient.writeData("/node-1", "node2");

        // /node-1 节点变化触发
        zkClient.subscribeChildChanges("/node-1", (s, node1_list) ->
                log.debug("节点名称:" + s + "->" + "当前的节点列表:" + node1_list));
        // 删除节点
        zkClient.delete("/node-1/node-1-1");

        // 级联删除节点
        zkClient.deleteRecursive("/node-1");
    }
}

Curator

<dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <version>5.1.0</version>
</dependency>
// 创建会话(同步,重试)
CuratorFrameworkFactory.newClient(String connectString, int sessionTimeoutMs,  int connectionTimeoutMs, RetryPolicy retryPolicy)
CuratorFrameworkFactory.builder().connectString("192.168.11.56:2180")  
		        .sessionTimeoutMs(30000).connectionTimeoutMs(30000)  
		        .canBeReadOnly(false)  
		        .retryPolicy(new ExponentialBackoffRetry(1000, Integer.MAX_VALUE))
		        .build();
// retryPolicy 连接策略:
// * RetryOneTime: 只重连一次.
// * RetryNTime: 指定重连的次数N.
// * RetryUtilElapsed: 指定最大重连超时时间和重连时间间隔,间歇性重连直到超时或者链接成功.
// * ExponentialBackoffRetry: 基于"backoff"方式重连,和 RetryUtilElapsed 的区别是重连的时间间隔是动态的.
// * BoundedExponentialBackoffRetry: 同 ExponentialBackoffRetry,增加了最大重试次数的控制.

// 创建节点
client.create().creatingParentIfNeeded()
               .withMode(CreateMode.PERSISTENT)
               .withACL(aclList)
               .forPath(path, "hello, zk".getBytes());
// 删除节点
client.delete().guaranteed().deletingChildrenIfNeeded().withVersion(version).forPath(path)
// 获取节点
client.getData().storingStatIn(stat).forPath(path);
client.getChildren().forPath(path);
// 更新节点
client.setData().withVersion(version).forPath(path, data)
// 判断节点是否存在
client.checkExists().forPath(path);
// 设置权限
Build.authorization(String scheme, byte[] auth)
client.setACL().withVersion(version)
               .withACL(ZooDefs.Ids.CREATOR_ALL_ACL)
               .forPath(path);
// 监听器(避免反复监听)
// * Cache 是 curator 中对事件监听的包装,对事件的监听可以近似看做是本地缓存视图和远程 zk 视图的对比过程
// * NodeCache 节点缓存用于处理节点本身的变化 ,回调接口 NodeCacheListener
// * PathChildrenCache  子节点缓存用于处理节点的子节点变化,回调接口 PathChildrenCacheListener
// * TreeCache  NodeCache 和 PathChildrenCache 的结合体,回调接口 TreeCacheListener
// 事务支持(保证一组操作的原子性)
Collection<CuratorTransactionResult> results = client.transaction().forOperations(operations);
// 异步支持
//    引入BackgroundCallback接口,用于处理异步接口调用之后服务端返回的结果信息
   public void processResult(CuratorFramework client, CuratorEvent event)
// * CuratorEventType 事件类型
// * org.apache.zookeeper.KeeperException.Code  服务器响应码(标识结果)

链接

@Slf4j
public class CuratorDemo {
    private final static String CONNECTIONS_TR = "192.168.199.128:2181";

    public CuratorFramework getInstance() {
        return CuratorFrameworkFactory.builder()
                .connectString(CONNECTIONS_TR)
                .sessionTimeoutMs(5000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
    }
}

增删改查

@Slf4j
public class CuratorDemo {
    public void crud() {
        CuratorFramework curatorFramework = getInstance();
        curatorFramework.start();
        log.debug("链接成功");

        // 创建节点
        try {
            String result = curatorFramework.create()
                    .creatingParentsIfNeeded()
                    .withMode(CreateMode.PERSISTENT)
                    .forPath("/curator/curator1/curator11", "123".getBytes());
            log.debug("创建节点:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 查询
        try {
            Stat stat = new Stat();
            byte[] bytes = curatorFramework.getData().storingStatIn(stat).forPath("/curator11");
            log.debug("查询:" + new String(bytes) + "-->stat:" + stat);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 更新
        try {
            Stat stat = curatorFramework.setData().forPath("/curator11", "123321".getBytes());
            log.debug("更新:" + stat);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 删除节点
        try { //默认情况下,version为 -1
            curatorFramework.delete().deletingChildrenIfNeeded().forPath("/curator");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

异步操作

@Slf4j
public class CuratorDemo {
    public void asynchronous() {
        CuratorFramework curatorFramework = getInstance();
        curatorFramework.start();
        log.debug("链接成功");
        
        // 异步操作
        ExecutorService service = Executors.newFixedThreadPool(1);
        CountDownLatch countDownLatch = new CountDownLatch(1);
        try {
            curatorFramework.create().creatingParentsIfNeeded().withMode(CreateMode.EPHEMERAL).
                    inBackground((curatorFramework1, curatorEvent) -> {
                        log.debug(Thread.currentThread().getName() + "->resultCode:" + curatorEvent.getResultCode() + "->" + curatorEvent.getType());
                        countDownLatch.countDown();
                    }, service).forPath("/node", "123".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }
    }
}

事务操作

@Slf4j
public class CuratorDemo {
    public void transaction() {
        CuratorFramework curatorFramework = getInstance();
        curatorFramework.start();
        log.debug("链接成功");

        // 事务操作(curator独有的)
        try {
            // TODO inTransaction 方法已弃用
            Collection<CuratorTransactionResult> resultCollections = curatorFramework.inTransaction()
                    .create().forPath("/demo1", "111".getBytes())
                    .and().setData().forPath("/demo1", "222".getBytes())
                    .and().commit();
            for (CuratorTransactionResult result : resultCollections) {
                log.debug(result.getForPath() + "->" + result.getType());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

监听

@Slf4j
public class CuratorDemo {
    /**
     * 三种 watcher 来做节点的监听
     * pathcache   监视一个路径下子节点的创建、删除、节点数据更新
     * NodeCache   监视一个节点的创建、更新、删除
     * TreeCache   pathcaceh+nodecache 的合体(监视路径下的创建、更新、删除事件),
     * 缓存路径下的所有子节点的数据
     */
    public void watcher() {
        CuratorFramework curatorFramework = getInstance();
        curatorFramework.start();
        log.debug("链接成功");

        // 节点变化 NodeCache
        // TODO NodeCache 已弃用
        try {
            NodeCache cache = new NodeCache(curatorFramework, "/curator", false);
            cache.start(true);
            cache.getListenable().addListener(() -> log.debug("节点数据发生变化,变化后的结果" +
                    ":" + new String(cache.getCurrentData().getData())));
            curatorFramework.setData().forPath("/curator", "123".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }


        // PatchChildrenCache
        // TODO PatchChildrenCache 已弃用
        try {
            PathChildrenCache cache = new PathChildrenCache(curatorFramework, "/event", true);
            cache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
            // Normal / BUILD_INITIAL_CACHE /POST_INITIALIZED_EVENT
            cache.getListenable().addListener((curatorFramework1, pathChildrenCacheEvent) -> {
                switch (pathChildrenCacheEvent.getType()) {
                    case CHILD_ADDED:
                        log.debug("增加子节点");
                        break;
                    case CHILD_REMOVED:
                        log.debug("删除子节点");
                        break;
                    case CHILD_UPDATED:
                        log.debug("更新子节点");
                        break;
                    default:
                        break;
                }
            });
            curatorFramework.create().withMode(CreateMode.PERSISTENT).forPath("/event", "event".getBytes());
            TimeUnit.SECONDS.sleep(1);
            log.debug("1");
            
            curatorFramework.create().withMode(CreateMode.EPHEMERAL).forPath("/event/event1", "1".getBytes());
            TimeUnit.SECONDS.sleep(1);
            log.debug("2");

            curatorFramework.setData().forPath("/event/event1", "222".getBytes());
            TimeUnit.SECONDS.sleep(1);
            log.debug("3");

            curatorFramework.delete().forPath("/event/event1");
            log.debug("4");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

弃用的方法日后补上。

posted @ 2021-01-28 09:37  qianbuhan  阅读(317)  评论(0)    收藏  举报