本文主要介绍使用 ZkClient 访问 ZooKeeper 的一些基本方法。这里安装的 ZooKeeper 版本是 zookeeper-3.4.14 稳定版。需要注意的是,ZkClient 已经将 ZooKeeper 原生 API 中的异步处理进行了同步化。

三种 zk 客户端对比:

原生api

zkclient

apache curator

优点

1、session会话超时重连;

2、解决watcher反复注册;

3、简化api开发;

1、解决watch注册一次就会失效的问题;

2、api更加简单易用;

3、提供了更多解决方案并且实现简单,比如分布式锁;

4、提供了常用的zk工具类;

缺点

1、watch注册一次后会失效;

2、session超时之后没有实现重连机制;

3、异常处理繁琐;

4、只提供了简单的byte[]数组的接口,没有提供针对对象级别的序列化;

5、创建节点时如果节点存在抛出异常,需要自行检查节点是否存在;

6、删除节点无法实现级联删除;

1、异常处理简化,抛出RuntimeException;

2、重试机制比较难用;

3、没有提供各种使用场景的实现;

1.ZkClient的使用

使用 ZkClient 需要添加 Maven 依赖(ZooKeeper 版本需要与服务器实例中 ZooKeeper 版本一致):

<!-- zookeeper -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.14</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!-- zkclient -->
<dependency>
    <groupId>com.101tec</groupId>
    <artifactId>zkclient</artifactId>
    <version>0.11</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>

1.创建会话

ZkClient提供了 7 中创建会话的方法:

public ZkClient(String serverstring)

public ZkClient(String zkServers, int connectionTimeout)

public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout)

public ZkClient(String zkServers, int sessionTimeout, int connectionTimeout, ZkSerializer zkSerializer)

public ZkClient(final String zkServers, final int sessionTimeout, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout)

public ZkClient(IZkConnection connection)

public ZkClient(IZkConnection connection, int connectionTimeout)

public ZkClient(IZkConnection zkConnection, int connectionTimeout, ZkSerializer zkSerializer)

public ZkClient(final IZkConnection zkConnection, final int connectionTimeout, final ZkSerializer zkSerializer, final long operationRetryTimeout)

其中一个参数 IZkConnection 是一个接口的定义。它是对 ZooKeeper 原生接口最直接的包装。在此接口下面有两个实现方法 ZkConnection 和 InMemoryConnection。一般使用 ZkConnection 方法就可以解决大部分的常见业务需求。参数 ZkSerializer 同样是一个接口,定义了 byte 数组序列化和反序列化的两个方法。如果不传递此参数,则默认使用org.I0Itec.zkclient.serialize.SerializableSerializer 实现类进行序列化。某些情况下此序列化接口会出现问题,比如乱码。此时,开发者可以直接实现 ZkSerializer 接口,重写自己的序列化方法。比如使用 Hessian 或 Kryo 等。

ZkClient zkClient = new ZkClient("localhost:2181,localhost:2182", 3000);

2.创建节点

ZkClient 提供了 15 个创建节点的方法:

public void createPersistent(String path)

public void createPersistent(String path, boolean createParents)

public void createPersistent(String path, boolean createParents, List<ACL> acl)

public void createPersistent(String path, Object data)

public void createPersistent(String path, Object data, List<ACL> acl)

public String createPersistentSequential(String path, Object data)

public String createPersistentSequential(String path, Object data, List<ACL> acl) 

public void createEphemeral(final String path)

public void createEphemeral(final String path, final List<ACL> acl)

public String create(final String path, Object data, final CreateMode mode)

public String create(final String path, Object data, final List<ACL> acl, final CreateMode mode) 

public void createEphemeral(final String path, final Object data)

public void createEphemeral(final String path, final Object data, final List<ACL> acl)

public String createEphemeralSequential(final String path, final Object data)

public String createEphemeralSequential(final String path, final Object data, final List<ACL> acl)

其实这些创建节点的方法都是对原生 API 的一层封装而已,底层实现基本相同。值得留意的一点是,原生API的参数通过 byte[] 来传递节点内容,而 ZkClient 支持自定义序列化,因此可以传输 Object 对象。

createParents 参数决定了是否递归创建父节点。true 表示递归创建,false 表示不使用递归创建。帮助开发人员省去了不少繁琐的检查和创建父节点的过程。

3.删除节点

删除节点提供了以下方法:

public boolean delete(final String path)

public boolean delete(final String path, final int version)

public boolean deleteRecursive(String path)

删除API其实很简单,重点说一下deleteRecursive接口,这个接口提供了递归删除的功能。在原生API中,如果一个节点存在子节点,那么它将无法直接删除,必须一层层遍历先删除全部子节点,然后才能将目标节点删除。

4.读取节点

获取节点列表:

public List<String> getChildren(String path)

此接口返回子节点的相对路径列表。比如节点路径为 /business/search和 /business/cart,那么当 path 为/business 时,返回的结果为 [search, cart]。

其中在原始 API 中,对节点注册 Watcher,当节点被删除或其下面的子节点新增或删除时,会通知客户端。ZkClient 则通过 Listener 来实现 Wather 注册,从 API 级别来支持 Watcher 监听的注册。

5.获取节点内容

public <T extends Object> T readData(String path)

public <T extends Object> T readData(String path, boolean returnNullIfPathNotExists)

public <T extends Object> T readData(String path, Stat stat)

通过方法返回参数的定义得知,返回的结果(节点的内容)已经被反序列化成对象了。

对本接口实现监听的接口为 IZkDataListener,分别提供了处理数据变化和删除操作的监听:

public void handleDataChange(String dataPath, Object data) throws Exception;

public void handleDataDeleted(String dataPath) throws Exception;

6.更新数据

更新操作可以通过以下接口来实现:

public void writeData(String path, Object object)

public void writeData(final String path, Object datat, final int expectedVersion)

public Stat writeDataReturnStat(final String path, Object datat, final int expectedVersion)

7.监测节点是否存在

protected boolean exists(final String path, final boolean watch)

8.注册监听

在 ZkClient 中客户端可以通过注册相关的事件监听来实现对 Zookeeper 服务端时间的订阅。其中 ZkClient 提供的监听事件接口有以下几种:

接口类

注册监听方法

解除监听方法

IZkChildListener

ZkClient 的 subscribeChildChanges 方法

ZkClient 的 unsubscribeChildChanges 方法

IZkDataListener

ZkClient 的 subscribeDataChanges 方法

ZkClient 的 subscribeChildChanges 方法

IZkStateListener

ZkClient 的 subscribeStateChanges 方法

ZkClient 的 unsubscribeStateChanges 方法

其中 ZkClient 还提供了一个 unsubscribeAll 方法,来解除所有监听。

下面以 IZkChildListener 为例来举例说明使用方法:

// 多个地址逗号隔开
ZkClient zkClient = new ZkClient("localhost:2181",3000);

String path = "/business";
// 注册子节点变更监听(此时path节点并不存在,但可以进行监听注册)
zkClient.subscribeChildChanges(path, new IZkChildListener() {
	public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
		System.out.println("路径" + parentPath +"下面的子节点变更, 子节点为:" + currentChilds );
	}
});

// 递归创建子节点(此时父节点并不存在)
zkClient.createPersistent("/business/search",true);
Thread.sleep(5000);
System.out.println(zkClient.getChildren(path));

执行结果为:

路径/business下面的子节点变更, 子节点为:[search]
[search]