
6.4 ZooKeeper Java API操作
除了可以使用命令行方式对ZooKeeper进行操作外,ZooKeeper还提供了Java API操作接口。下面对ZooKeeper的常用Java API接口进行介绍。
6.4.1 创建Java工程
在编写Java API之前,首先需要新建一个ZooKeeper项目。ZooKeeper项目的结构与普通的JavaSE项目一样,只是依赖的jar包不同。
1. Maven项目
在Eclipse中新建一个Maven项目zk_demo(Maven项目的搭建此处不做过多讲解),项目结构如图6-5所示。

图6-5 ZooKeeper Maven项目结构
然后在该项目的pom.xml文件中添加以下代码,以引入ZooKeeper的Java API依赖包:

配置好pom.xml后,即可进行ZooKeeper Java API的编写。
2. 普通JavaSE项目
若用户不想使用Maven构建项目,也可以创建普通JavaSE项目。普通JavaSE项目依赖的ZooKeeper jar包主要有ZooKeeper核心包zookeeper-3.4.10.jar和三个日志包slf4j-log4j12-1.6.1.jar、slf4j-api-1.6.1.jar、log4j-1.2.16.jar。这4个jar包都可以在ZooKeeper安装文件zookeeper-3.4.10.tar.gz中找到。其中核心包在安装文件解压后的根目录,日志包在根目录下的lib文件夹中。
一个ZooKeeper的普通JavaSE项目结构如图6-6所示。

图6-6 ZooKeeper普通JavaSE项目结构
6.4.2 创建节点
ZooKeeper创建节点不支持递归调用,即无法在父节点不存在的情况下创建一个子节点,如在/zk01节点不存在的情况下创建/zk01/ch01节点,并且如果一个节点已经存在,那么创建同名节点时,会抛出NodeExistsException异常。
下面创建一个节点/zk001,节点的元数据为“zk001_data”,步骤如下。
1. 编写代码
在新建的zk_demo项目中新建Java类CreatePath.java,然后在main()方法中写入创建节点的代码。完整代码如下所示:

2. 程序解读
❶ 新建一个ZooKeeper对象,传入三个参数,解析如下:
- 第一个参数为以逗号分隔的服务器连接字符串,格式为“IP地址:端口”或“主机名:端口”(使用主机名,需要在系统本地配置主机名IP映射),这里需要把所有的ZooKeeper服务器的地址都写上,而不是只写其中一台。ZooKeeper客户端对象将从连接串中挑选任意一个服务器进行连接,如果连接失败,将尝试连接另外一个服务器,直到建立连接。这样的好处是能保证ZooKeeper服务的高可靠性,防止因为其中一台服务器宕机而导致连接失败。
- 第二个参数为连接超时时间,这里是3秒。
- 第三个参数为观察者对象,连接成功后会调用观察者对象中的回调方法,这里传入null即可。
❷ 调用ZooKeeper对象的创建节点方法create(),返回创建的节点路径,并需要传入4个参数,解析如下:
- 第一个参数为节点名称。
- 第二个参数为节点数据,需要转成字节数组。
- 第三个参数为权限控制,这里使用ZooKeeper自带的完全开放权限Ids.OPEN_ACL_UNSAFE。
- 第四个参数为创建模式CreateMode,它是一个枚举类型,共有4个取值:PERSISTENT(持久节点,这种目录节点存储的数据不会丢失 ,即客户端失去连接之后不会被自动删除)、PERSISTENT_SEQUENTIAL(持久顺序节点,这种节点在命名上会自动编号,根据当前已经存在的节点数自动加1)、EPHEMERAL(临时节点,客户端断开连接时,这种节点会被自动删除)、EPHEMERAL_SEQUENTIAL(临时顺序节点,客户端断开连接时,这种节点也会被自动删除)。
create()方法的定义源码如下:

CreateMode枚举类的部分源码如下:

3. 运行程序
直接在Eclipse中右击运行该程序即可,观察控制台的输出结果。若能成功输出节点路径,说明创建成功。
6.4.3 修改数据
使用ZooKeeper对象的setData()方法可以修改节点的元数据。例如,将节点/zk001的元数据修改为“zk001_data_new”,示例代码如下:

setData()方法的三个参数解析如下:
- 第一个参数为节点路径。
- 第二个参数为需要修改的元数据,并转成字节数组。
- 第三个参数为版本号,-1代表所有版本。
数据添加(或修改)成功后,会返回节点的状态信息到Stat对象中,stat.getVersion()表示获取该节点的版本号,默认新节点的版本号为0,每次对节点进行修改,版本号都会增加1。
setData()方法的定义源码如下:

6.4.4 获取数据
使用ZooKeeper对象的getData()方法可以获得指定节点的元数据,示例代码如下:

上述代码获取了节点/zk002的元数据,并将该节点的状态信息放入了对象stat中,最后将元数据转成字符串输出到控制台。如需查看节点状态信息,可以从对象stat中进行输出查看。
getData()方法的第二个参数传入的是null,也可以指定一个观察者对象Watcher,对节点数据的变化进行监听,一旦有数据改变,就会触发Watcher指定的回调方法。
对上述代码进行改进,加入观察者回调方法后,代码如下:

上述代码分析如下:
❶ process()方法是Watcher接口中的一个抽象方法,当ZooKeeper向客户端发送一个Watcher 事件通知时,客户端就会对相应的 process()方法进行回调,从而实现对事件的处理。
process()方法包含WatchedEvent类型的参数,WatchedEvent 包含了每一个事件的三个基本属性:通知状态(KeeperState)、事件类型(EventType)、节点路径(Path),ZooKeeper 使用WatchedEvent 对象来封装服务端事件并传递给 Watcher,从而方便回调方法process() 对服务端事件进行处理。
process()方法中通过代码System.out.println(event.getType());输出服务端的事件类型,此处控制台的输出结果为NodeDataChanged。从结果单词的含义可知,节点数据被改变了。
❷ 为了能够更好地验证是否触发了Watcher,不让程序一次执行到底,从而加入了此部分代码,让程序一直停留在此处。
上述代码实现了一次性监听,当触发Watcher后不会再次触发,若需要持续进行监听,可将上述代码进行改进:定义一个Watcher对象,在process()方法中重新设置监听,当ZooKeeper节点/zk002的状态发生改变时将会触发Watcher,输出改变的事件类型。改进后的代码如下:

getData()方法的定义源码如下:

Watcher接口的源码如下:



6.4.5 删除节点
使用ZooKeeper对象的delete()方法可以对指定路径节点进行删除。例如,删除节点/zk002,代码如下:

上述代码中,delete()方法的两个参数解析如下:
- 第一个参数为需要删除的节点路径。
- 第二个参数为节点版本,-1代表删除所有版本。
delete()方法的定义源码如下:
