前言
本篇主要讲解Java中如何操作Zookeeper,包括同步阻塞式的API和异步非阻塞式的API两大类。完整代码请点击【源代码】目录。
前置阅读
- zookeeper从入门到放弃 这篇文章讲解了zookeeper基础知识,以及本文所用到的zookeeper集群的搭建详细过程。
实验环境
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
<!-- 本篇使用的工具类,如无需要可以自行编写相关代码 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
同步阻塞
Java操作zookeeper无非就是两个步骤:连接和操作。
连接
首先看下连接部分
// 常量/静态变量部分
private static final String[] ZK_SERVER = {
"192.168.1.101:2181", "192.168.1.102:2181",
"192.168.1.103:2181", "192.168.1.104:2181",
};
private static final int SESSION_TIMEOUT = 3000;
private static final CountDownLatch latch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
// 连接部分
/**
* 初始化连接
*/
private static void init(String[] socketArr) throws InterruptedException, IOException {
zooKeeper = new ZooKeeper(Joiner.on(",").join(socketArr), SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
Event.KeeperState state = event.getState();
/**
* 此处并不是所有的case
* @see Watcher.Event.KeeperState
*/
switch (state) {
case SyncConnected:
System.out.println("ZooKeeper SyncConnected");
latch.countDown();
break;
default:
System.out.println("ZooKeeper default");
break;
}
}
});
// 等Zookeeper连接完成后再获取
latch.await();
}
创建Zookeeper实例的时候有三个参数,方法定义如下
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
除了要连接的ZK server、超时时间,还有一个Watcher
,这个对象用来处理一系列监听事件。但是,此处传入的Watcher
是session
级别的,跟path/Znode
无关。也就是说不能监听path/Znode
的变化,只能监听连接。
Zookeeper对象的创建和连接是异步的,也就是通过new ZooKeeper
的方式拿到的Zookeeper对象,可能是还没有连接上ZK server的对象。所以需要CountDownLatch
工具类结合Watcher
上的SyncConnected
事件,在连接完成后再返回Zookeeper对象。
操作
有了zookeeper对象之后,就可以操作zookeeper了。
/**
* 创建持久非序列节点
*/
public static String create(String path, byte[] data) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
return zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
create
方法定义如下
public String create(final String path, byte data[], List<ACL> acl, CreateMode createMode)
有四个参数,分别代表创建的Znode、数据、权限acl、Znode类型。ZooDefs.Ids.OPEN_ACL_UNSAFE
表示不需要权限,CreateMode.PERSISTENT
表示创建持久非序列节点
/**
* 删除节点
*/
public static void delete(String path) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.delete(path, -1);
}
delete
方法定义如下
public void delete(final String path, int version)
path
表示要删除的Znode,version
表示数据版本,如果给定的版本为-1,则它匹配任何节点的版本
/**
* 更新数据
*/
public static Stat set(String path, byte[] data) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
return zooKeeper.setData(path, data, -1);
}
setData
方法定义如下
public Stat setData(final String path, byte data[], int version)
其中path
表示要修改的Znode,data[]
表示更新的数据,version
表示数据版本,如果给定的版本为-1,则它匹配任何节点的版本
/**
* 获取数据
*/
public static byte[] get(String path) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
Stat stat = new Stat();
byte[] data = zooKeeper.getData(path, null, stat);
System.out.println("Zxid is: 0x" + Long.toHexString(stat.getCzxid()));
return data;
}
getData
方法定义如下
public byte[] getData(String path, boolean watch, Stat stat)
public byte[] getData(final String path, Watcher watcher, Stat stat)
path
表示要获取的Znode,Stat
表示节点元数据(metadata
)。第二个参数如果是boolean类型,表示是否需要监听连接事件(和New Zookeeper
时一样的事件),而传入Watcher
对象也表示事件监听器,这里监听的事件是Znode节点被修改的事件,不同于new Zookeeper
时的监听。并且这个监听器只能在查询时传入(getData
、exist
),并且是一次性的,想要继续监听,需要再次传入监听器。
在craete
的时候,如果Znode已经存在,会抛出异常,在delete
、setData
、getData
的时候,如果Znode不存在,也会抛出异常。
源代码
package com.sicimike.zk;
import com.google.common.base.Joiner;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* @author sicimike
* @create 2020-05-03 14:17
*/
public class ZookeeperSyncUtil {
private static final String[] ZK_SERVER = {
"192.168.1.101:2181", "192.168.1.102:2181",
"192.168.1.103:2181", "192.168.1.104:2181",
};
private static final int SESSION_TIMEOUT = 3000;
private static final CountDownLatch latch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
/**
* 获取zookeeper实例
*
* @param socketArr
* @return
* @throws InterruptedException
* @throws IOException
*/
private static void init(String[] socketArr) throws InterruptedException, IOException {
zooKeeper = new ZooKeeper(Joiner.on(",").join(socketArr), SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
Event.KeeperState state = event.getState();
/**
* 此处并不是所有的case
* @see Watcher.Event.KeeperState
*/
switch (state) {
case SyncConnected:
System.out.println("ZooKeeper SyncConnected");
latch.countDown();
break;
default:
System.out.println("ZooKeeper default");
break;
}
}
});
// 等Zookeeper连接完成后再获取
latch.await();
}
/**
* 创建持久非序列节点
*
* @param path
* @param data
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public static String create(String path, byte[] data) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
return zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
/**
* 删除节点
*
* @param path
* @throws KeeperException
* @throws InterruptedException
*/
public static void delete(String path) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.delete(path, -1);
}
/**
* 更新数据
*
* @param path
* @param data
* @return
* @throws KeeperException
* @throws InterruptedException
* @throws IOException
*/
public static Stat set(String path, byte[] data) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
return zooKeeper.setData(path, data, -1);
}
/**
* 获取数据
*
* @param path
* @return
* @throws KeeperException
* @throws InterruptedException
*/
public static byte[] get(String path) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
Stat stat = new Stat();
byte[] data = zooKeeper.getData(path, false, stat);
System.out.println("Zxid is: 0x" + Long.toHexString(stat.getCzxid()));
return data;
}
/**
*
* @param path
* @param watcher
* @return
* @throws KeeperException
* @throws InterruptedException
* @throws IOException
*/
public static byte[] get(String path, Watcher watcher) throws KeeperException, InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
Stat stat = new Stat();
return zooKeeper.getData(path, watcher, stat);
}
}
测试代码
package com.sicimike.zk;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import java.io.IOException;
/**
* @author sicimike
* @create 2020-05-03 12:05
*/
public class ZookeeperDemo {
public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
String path = ZookeeperSyncUtil.create("/sicimike", "hello world".getBytes());
byte[] bytes = ZookeeperSyncUtil.get(path, new Watcher() {
@Override
public void process(WatchedEvent event) {
/**
* @see org.apache.zookeeper.Watcher.Event.EventType
*/
switch (event.getType()) {
case NodeDataChanged:
System.out.println("Zookeeper NodeDataChanged event");
break;
}
}
});
System.out.println(new String(bytes));
ZookeeperSyncUtil.set(path, "hello sicimike".getBytes());
bytes = ZookeeperSyncUtil.get(path);
System.out.println(new String(bytes));
ZookeeperSyncUtil.delete(path);
}
}
执行结果
ZooKeeper SyncConnected
hello world
Zookeeper NodeDataChanged event
Zxid is: 0x50000006b
hello sicimike
异步非阻塞
zookeeper还提供了一套异步非阻塞的API,使用方式和阻塞式的差不多,只是之前是通过返回值的形式告诉调用者操作的结果,现在是通过回调的方式,直接给出源代码
源代码
package com.sicimike.zk;
import com.google.common.base.Joiner;
import org.apache.zookeeper.*;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* @author sicimike
* @create 2020-05-03 16:32
*/
public class ZookeeperAsyncUtil {
private static final String[] ZK_SERVER = {
"192.168.1.101:2181", "192.168.1.102:2181",
"192.168.1.103:2181", "192.168.1.104:2181",
};
private static final int SESSION_TIMEOUT = 3000;
private static final CountDownLatch latch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
/**
* 获取zookeeper实例
*
* @param socketArr
* @return
* @throws InterruptedException
* @throws IOException
*/
private static void init(String[] socketArr) throws InterruptedException, IOException {
zooKeeper = new ZooKeeper(Joiner.on(",").join(socketArr), SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent event) {
Event.KeeperState state = event.getState();
/**
* 此处并不是所有的case
* @see Event.KeeperState
*/
switch (state) {
case SyncConnected:
System.out.println("ZooKeeper SyncConnected");
latch.countDown();
break;
default:
System.out.println("ZooKeeper default");
break;
}
}
});
// 等Zookeeper连接完成后再获取
latch.await();
}
public static void create(String path, byte[] data, AsyncCallback.StringCallback callback, Object ctx) throws InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL, callback, ctx);
}
public static void delete(String path, AsyncCallback.VoidCallback callback, Object ctx) throws InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.delete(path, -1, callback, ctx);
}
public static void set(String path, byte[] data, AsyncCallback.StatCallback callback, Object ctx) throws InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.setData(path, data, -1, callback, ctx);
}
public static void get(String path, AsyncCallback.DataCallback callback, Object ctx) throws InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.getData(path, false, callback, ctx);
}
public static void get(String path, Watcher watcher, AsyncCallback.DataCallback callback, Object ctx) throws InterruptedException, IOException {
if (zooKeeper == null) {
init(ZK_SERVER);
}
zooKeeper.getData(path, watcher, callback, ctx);
}
}
其中Zookeeper对象的获取还是阻塞的,有兴趣的同学可以改成异步的。
测试代码
package com.sicimike.zk;
import org.apache.zookeeper.AsyncCallback;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
/**
* @author sicimike
* @create 2020-05-03 12:05
*/
public class ZookeeperDemo {
public static void main(String[] args) throws IOException, InterruptedException {
ZookeeperAsyncUtil.create("/sicimike", "hello".getBytes(), new AsyncCallback.StringCallback() {
@Override
public void processResult(int rc, String path, Object ctx, String name) {
System.out.println("create: " + rc);
System.out.println("create: " + path);
System.out.println("create: " + ctx);
System.out.println("create: " + name);
}
}, "I am create ctx");
ZookeeperAsyncUtil.set("/sicimike", "hello world".getBytes(), new AsyncCallback.StatCallback() {
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("set: " + rc);
System.out.println("set: " + path);
System.out.println("set: " + ctx);
System.out.println("set: 0x" + Long.toHexString(stat.getCzxid()));
}
}, "I am set ctx");
ZookeeperAsyncUtil.get("/sicimike", new Watcher() {
@Override
public void process(WatchedEvent event) {
/**
* @see org.apache.zookeeper.Watcher.Event.EventType
*/
switch (event.getType()) {
case NodeDeleted:
System.out.println("Zookeeper NodeDeleted event");
break;
}
}
}, new AsyncCallback.DataCallback() {
@Override
public void processResult(int rc, String path, Object ctx, byte[] data, Stat stat) {
System.out.println("get: " + rc);
System.out.println("get: " + path);
System.out.println("get: " + ctx);
System.out.println("get: " + new String(data));
System.out.println("get: 0x" + Long.toHexString(stat.getCzxid()));
}
}, "I am get ctx");
ZookeeperAsyncUtil.delete("/sicimike", new AsyncCallback.VoidCallback() {
@Override
public void processResult(int rc, String path, Object ctx) {
System.out.println("delete: " + rc);
System.out.println("delete: " + path);
System.out.println("delete: " + ctx);
}
}, "I am delete ctx");
// 阻塞主线程
System.in.read();
}
}
执行结果
ZooKeeper SyncConnected
create: 0
create: /sicimike
create: I am create ctx
create: /sicimike
set: 0
set: /sicimike
set: I am set ctx
set: 0x50000008d
get: 0
get: /sicimike
get: I am get ctx
get: hello world
get: 0x50000008d
Zookeeper NodeDeleted event
delete: 0
delete: /sicimike
delete: I am delete ctx
可以看到改成异步非阻塞的API之后,调用方变得复杂了起来。
回调函数的参数大致有如下几个
int rc
:表示返回码或调用结果,0
表示成功,具体的值参考org.apache.zookeeper.KeeperException.Code
类String path
:传递给异步调用的路径Object ctx
:传递给异步调用的任何上下文对象String name
:创建的Znode名称,如果创建的是非序列节点,则和path
相同byte[] data
:Znode数据Stat stat
:Znode元数据
总结
本文主要讲解Java对Zookeeper简单的操作,包括同步阻塞的方式和异步回调的方式,以及Zookeeper的事件监听器的使用。