zookeeper常用 java api
- zk原生api
- 连接的创建是异步的,需要开发人员自行编码实现等待
- 不支持自动超时重连,需要手动重连会话
- Watcher注册一次后失效,需要手动重新Watcher
- 不支持递归创建多级目录
- zkClient
- session会话超时重连
- 解决Watcher反复注册
- 简化API开发
- Apache Curator
- 包含zClient提供的功能
- 提供更多复杂问题的解决方案,如:分布式锁
- 提供常用Zookeeper工具类
- 提供Fluent编程风格(对象.对象.对象.对象…)
Curator API
导入 maven 依赖
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.12</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.2.0</version>
</dependency>
根对象和zookeeper集群地址
// ck客户端实例
protected CuratorFramework client;
// zk的集群地址
private static final String ZK_URL = "192.168.184.10:2181,192.168.184.20:2181,192.168.184.30:2181";
建立连接
/**
* 构造的时候建立连接
*/
public CuratorService() {
// 每间隔1000ms重试连接一次,最多重连3次
RetryPolicy retryPolicy = new RetryNTimes(3, 1000);
client = CuratorFrameworkFactory.builder().connectString(ZK_URL)
// 会话超时时间(保持发送心跳),超时会重连
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
// 声明命名空间(可选),所有节点会在该目录的基础下进行操作,类似于eclipse或idea的workspace
.namespace("seecen")
// 授权acl,后续创建acl多层节点时,如/a/b,,无法创建/b的节点,因为/a创建好后就需要auth授权才能创建/b
// 要访问带权限的节点之前,也需要先授权
.authorization("digest", "bigpig:123456".getBytes())
.build();
// 开启连接
client.start();
}
/**********[main调用]**********/
// 输出连接状态
System.out.println(curatorService.client.getState());
创建节点
/**
* 创建节点
* @param createMode 节点类型:持久节点,有序持久节点,临时节点,有序临时节点
* @param acl 访问控制权限
* @param path 路径,支持递归创建父级节点
* @param data 最后路径的数据
* @throws Exception
*/
public void createNode(CreateMode createMode, List<ACL> acl, String path, byte[] data) throws Exception {
// creatingParentsIfNeeded 支持递归创建节点,如 /aa/bb/cc
client.create().creatingParentsIfNeeded().withMode(createMode)
// 递归多层节点时候,acl也会赋给所有父节点(前提是父节点是新建的)
// 且创建者需要在链接的时候有授权 .authorization("digest", "bigpig:123456".getBytes())
.withACL(acl, true)
.forPath(path, data);
}
/**********[main调用]**********/
// 实例化并创建zk连接
CuratorService curatorService = neCuratorService();
// 该目录会创建在namespace下
curatorService.createNode(CreateMode.PERSISTENT,ZooDefs.Ids.OPEN_ACL_UNSAFE,"/oracle/aliyun/redhat", "test数据".getBytes("UTF-8"));
/**********[递归创建节点并且赋予acl权限]**********/
// 创建多层节点,并且全部设置权限。需要授权.authorization("digest", "bigpig:123456".getBytes())
String password = DigestAuthenticationProvider.generateDigest("bigpig:123456");
Id digest = new Id("digest", password);
// 授予创建和删除和写的权限
ACL acl1 = new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.DELETE | ZooDefs.Perms.WRITE, digest);
curatorService.createNode(CreateMode.PERSISTENT, Collections.singletonList(acl1), "/oracle/aliyun/redhat", "hello world".getBytes());
更新节点
/**
* 更新节点数据
* @param version 乐观锁的版本号,对应zk节点中的dataVersion,如果不一致会抛出异常
* @param path 要修改的节点路径
* @param data 要修改的新数据
* @throws Exception
*/
public void updateNode(int version, String path, byte[] data) throws Exception {
client.setData().withVersion(version).forPath(path, data);
}
/**********[main调用]**********/
// 修改数据,版本号不对,会报乐观锁异常, 0 为版本号对应dataVersion
curatorService.updateNode(0, "/oracle/aliyun/redhat", "更新数据".getBytes("UTF-8"));
删除节点
/**
* 删除节点
* @param version
* @param path
* @throws Exception
*/
public void deleteNode(int version, String path) throws Exception {
client.delete()
.guaranteed()// 保证删除成功。如果网络抖动,后台会继续删除,直到成功
.deletingChildrenIfNeeded()// 递归删除path下所有子节点路径
.withVersion(version)
.forPath(path);
}
/**********[main调用]**********/
// 删除数据,版本号不对,会报乐观锁异常,支持递归删除子节点,0为版本号对应dataVersion
curatorService.deleteNode(0, "/oracle");
判断存在并获取Stat信息
备注:stat信息如下所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dHICF8Ho-1573804097680)(2C804C16A63B4797B966AC566DC99B76)]
/**
* 判断一个路径是否存在,并返回Stat信息
* @param path 路径
* @return stat 即命令中stat返回的许多属性值
* @throws Exception
*/
public Stat exist(String path) throws Exception {
return client.checkExists().forPath(path);
}
/**********[main调用]**********/
// 判断路径节点是否存在,存在返回stat对象,不存在返回null
Stat stat = curatorService.exist("/oracle/aliyun/redhat");
获取数据
/**
* 获取指定路径的数据
* @param path 路径
* @return byte[]数据
* @throws Exception
*/
public byte[] getPathData(String path) throws Exception {
return client.getData().forPath(path);
}
/**********[main调用]**********/
// 获取数据
byte[] pathData = curatorService.getPathData("/oracle/aliyun/redhat");
System.out.println(new String(pathData));
获取所有子节点(不含孙节点)
/**
* 获取一个path下的子节点(不含孙节点)
* @param path 指定路径
* @return List<String> 子节点
* @throws Exception
*/
public List<String> getChildren(String path) throws Exception {
return client.getChildren().forPath(path);
}
/**********[main调用]**********/
// 获取路径的子节点,注意:不包含孙节点
List<String> children = curatorService.getChildren("/oracle");
for (String child : children) {
System.out.println(child);
}
一次性监听节点
该方式只能监听并触发一次
/**
* 监听路径变化,做出动作,注:该方式只会监听第一次
* @param path 要监听的路径节点
* @param watcher 监听对象,具体监听到path后要做的事情
* @throws Exception
*/
public void oneListen(String path, CuratorWatcher watcher) throws Exception {
client.getData().usingWatcher(watcher).forPath(path);
}
/**********[main调用]**********/
curatorService.oneListen("/oracle", new CuratorWatcher() {
public void process(WatchedEvent event) throws Exception {
System.out.println(event.getPath() + "发生了" + event.getType() + "事件!");
}
});
重复监听节点
/**
* 监听路径数据变化,做出动作,注:该方式每一次都会监听,且只对数据改变会触发
* @param path 要监听的路径节点
* @return NodeCache节点和数据对象,并且如果不想监听了,需要外面关闭它
* @throws Exception
*/
public NodeCache anyListen(String path) throws Exception {
final NodeCache nodeCache = new NodeCache(client, path);
nodeCache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
if(nodeCache.getCurrentData() != null) {
System.out.println(nodeCache.getPath() + ":" + new String(nodeCache.getCurrentData().getData()));
}
}
});
// 开始监听
nodeCache.start();
return nodeCache;
}
/**********[main调用]**********/
NodeCache nodeCache = curatorService.anyListen("/oracle");
监听子节点的CUD[create,update,delete]变化
/**
* 重复监听一个路径下的所有子节点(不含孙节点)的CRUD
* @param path 父节点路径
* @param pathChildrenCacheListener 监控对象
* @return PathChildrenCache 如果不想监听了,需要在外面关闭它
* @throws Exception
*/
public PathChildrenCache anyListenChildren(String path, PathChildrenCacheListener pathChildrenCacheListener) throws Exception {
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, true);
pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
// 异步初始化,初始化之后触发事件
pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
return pathChildrenCache;
}
/**********[main调用]**********/
// 重复监听子节点
PathChildrenCache pathChildrenCache = curatorService.anyListenChildren("/oracle", new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
if (event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED
|| event.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED
|| event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
System.out.println(event.getData().getPath() + ":" + new String(event.getData().getData()));
} else if (event.getType() == PathChildrenCacheEvent.Type.INITIALIZED) {
System.out.println("初始化OK");
} else if (event.getType() == PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED) {
System.out.println("连接成功");
}
}
});
给已经存在的节点授权
/**
* 给已经存在的路径节点授权,支持递归.前提需要认证了父节点的权限
* @param path
* @param acls
* @throws Exception
*/
public void grantAcl(String path,int aclVersion, List<ACL> acls) throws Exception{
client.setACL().withVersion(aclVersion).withACL(acls).forPath(path);
}
/**********[main调用]**********/
String path = "/oracle/aliyun/redhat";
String password = DigestAuthenticationProvider.generateDigest("bigpig:123456");
Id digest = new Id("digest", password);
ACL acl1 = new ACL(ZooDefs.Perms.ALL, digest);
Stat stat = curatorService.exist(path);
if (stat != null) {
// 需要对应stat中的aclVersion
curatorService.grantAcl(path, stat.getAversion(), Collections.singletonList(acl1));
}
释放资源
如果要关闭client对象或NodeCache监听对象, 可以使用提供好的工具类CloseableUtils
CloseableUtils.closeQuietly(client);
CloseableUtils.closeQuietly(nodeCache);
完整代码
- CuratorService.java
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.recipes.cache.NodeCacheListener;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
import java.util.List;
/**
* Curator封装类
*/
public class CuratorService {
// ck客户端实例
protected CuratorFramework client;
// zk的集群地址
private static final String ZK_URL = "192.168.184.10:2181,192.168.184.20:2181,192.168.184.30:2181";
/**
* 构造的时候建立连接
*/
public CuratorService() {
// 每间隔1000ms重试连接一次,最多重连3次
RetryPolicy retryPolicy = new RetryNTimes(3, 1000);
client = CuratorFrameworkFactory.builder().connectString(ZK_URL)
// 会话超时时间(保持发送心跳),超时会重连
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
// 声明命名空间(可选),所有节点会在该目录的基础下进行操作,类似于eclipse或idea的workspace
.namespace("seecen")
// 授权acl,后续创建acl多层节点时,如/a/b,,无法创建/b的节点,因为/a创建好后就需要auth授权才能创建/b
// 要访问带权限的节点之前,也需要先授权
.authorization("digest", "bigpig:123456".getBytes())
.build();
// 开启连接
client.start();
}
/**
* 创建节点
* @param createMode 节点类型:持久节点,有序持久节点,临时节点,有序临时节点
* @param acl 访问控制权限
* @param path 路径,支持递归创建父级节点
* @param data 最后路径的数据
* @throws Exception
*/
public void createNode(CreateMode createMode, List<ACL> acl, String path, byte[] data) throws Exception {
// creatingParentsIfNeeded 支持递归创建节点,如 /aa/bb/cc
client.create().creatingParentsIfNeeded().withMode(createMode)
// 递归多层节点时候,acl也会赋给所有父节点(前提是父节点是新建的)
// 且创建者需要在链接的时候有授权 .authorization("digest", "bigpig:123456".getBytes())
.withACL(acl, true)
.forPath(path, data);
}
/**
* 更新节点数据
* @param version 乐观锁的版本号,对应zk节点中的dataVersion,如果不一致会抛出异常
* @param path 要修改的节点路径
* @param data 要修改的新数据
* @throws Exception
*/
public void updateNode(int version, String path, byte[] data) throws Exception {
client.setData().withVersion(version).forPath(path, data);
}
/**
* 删除节点
* @param version
* @param path
* @throws Exception
*/
public void deleteNode(int version, String path) throws Exception {
client.delete()
.guaranteed()// 保证删除成功。如果网络抖动,后台会继续删除,直到成功
.deletingChildrenIfNeeded()// 递归删除path下所有子节点路径
.withVersion(version)
.forPath(path);
}
/**
* 判断一个路径是否存在,并返回Stat信息
* @param path 路径
* @return stat 即命令中stat返回的许多属性值
* @throws Exception
*/
public Stat exist(String path) throws Exception {
return client.checkExists().forPath(path);
}
/**
* 获取指定路径的数据
* @param path 路径
* @return byte[]数据
* @throws Exception
*/
public byte[] getPathData(String path) throws Exception {
return client.getData().forPath(path);
}
/**
* 获取一个path下的子节点(不含孙节点)
* @param path 指定路径
* @return List<String> 子节点
* @throws Exception
*/
public List<String> getChildren(String path) throws Exception {
return client.getChildren().forPath(path);
}
/**
* 监听路径变化,做出动作,注:该方式只会监听第一次
* @param path 要监听的路径节点
* @param watcher 监听对象,具体监听到path后要做的事情
* @throws Exception
*/
public void oneListen(String path, CuratorWatcher watcher) throws Exception {
client.getData().usingWatcher(watcher).forPath(path);
}
/**
* 监听路径数据变化,做出动作,注:该方式每一次都会监听,且只对数据改变会触发
* @param path 要监听的路径节点
* @return NodeCache节点和数据对象,并且如果不想监听了,需要外面关闭它
* @throws Exception
*/
public NodeCache anyListen(String path) throws Exception {
final NodeCache nodeCache = new NodeCache(client, path);
nodeCache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
if(nodeCache.getCurrentData() != null) {
System.out.println(nodeCache.getPath() + ":" + new String(nodeCache.getCurrentData().getData()));
}
}
});
// 开始监听
nodeCache.start();
return nodeCache;
}
/**
* 监听一个路径下的所有子节点(不含孙节点)的CRUD
* @param path 父节点路径
* @param pathChildrenCacheListener 监控对象
* @return PathChildrenCache 如果不想监听了,需要在外面关闭它
* @throws Exception
*/
public PathChildrenCache anyListenChildren(String path, PathChildrenCacheListener pathChildrenCacheListener) throws Exception {
PathChildrenCache pathChildrenCache = new PathChildrenCache(client, path, true);
pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
// 异步初始化,初始化之后触发事件
pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
return pathChildrenCache;
}
/**
* 给已经存在的路径节点授权,支持递归.前提需要认证了父节点的权限
* @param path
* @param acls
* @throws Exception
*/
public void grantAcl(String path,int aclVersion, List<ACL> acls) throws Exception{
client.setACL().withVersion(aclVersion).withACL(acls).forPath(path);
}
}
- Test.java
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
import org.apache.curator.framework.recipes.cache.*;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.data.Stat;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test {
public static void main(String[] args) throws Exception {
// 实例化并创建zk连接
CuratorService curatorService = new CuratorService();
// 输出连接状态(STARTED)
System.out.println(curatorService.client.getState());
/*递归创建节点*/
// curatorService.createNode(CreateMode.PERSISTENT,
// ZooDefs.Ids.OPEN_ACL_UNSAFE,
// "/oracle/aliyun/redhat", // 注:该目录会创建在namespace下
// "test数据".getBytes("UTF-8"));
/*给已经存在的节点授权*/
// String path = "/oracle/aliyun/redhat";
// String password = DigestAuthenticationProvider.generateDigest("bigpig:123456");
// Id digest = new Id("digest", password);
// ACL acl1 = new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.DELETE | ZooDefs.Perms.WRITE, digest);
// ACL acl1 = new ACL(ZooDefs.Perms.ALL, digest);
// Stat stat = curatorService.exist(path);
// if (stat != null) {
// curatorService.grantAcl(path, stat.getAversion(), Collections.singletonList(acl1));
// }
/*递归创建节点并且赋予acl权限*/
// // 创建多层节点,并且全部设置权限。需要授权.authorization("digest", "bigpig:123456".getBytes())
// String password = DigestAuthenticationProvider.generateDigest("bigpig:123456");
// Id digest = new Id("digest", password);
// ACL acl1 = new ACL(ZooDefs.Perms.CREATE | ZooDefs.Perms.DELETE | ZooDefs.Perms.WRITE, digest);
// curatorService.createNode(CreateMode.PERSISTENT, Collections.singletonList(acl1), "/oracle/aliyun/redhat", "hello world".getBytes());
/*修改数据,版本号不对,会报乐观锁异常*/
// curatorService.updateNode(0, "/oracle/aliyun/redhat", "更新数据".getBytes("UTF-8"));
/* 删除数据,版本号不对,会报乐观锁异常,支持递归删除子节点*/
// curatorService.deleteNode(0, "/oracle");
/*获取数据*/
// byte[] pathData = curatorService.getPathData("/oracle/aliyun/redhat");
// System.out.println(new String(pathData));
/*判断路径节点是否存在,存在返回stat,不存在返回null*/
// Stat stat = curatorService.exist("/oracle/aliyun/redhat");
// System.out.println(stat.getVersion());
/*获取路径的子节点,注意:不包含孙节点*/
// List<String> children = curatorService.getChildren("/oracle");
// for (String child : children) {
// System.out.println(child);
// }
/*监听一次节点*/
// curatorService.oneListen("/oracle", new CuratorWatcher() {
// @Override
// public void process(WatchedEvent event) throws Exception {
// System.out.println(event.getPath() + "发生了" + event.getType() + "事件!");
// }
// });
/*重复监听节点*/
// NodeCache nodeCache = curatorService.anyListen("/oracle");
// 监听10S后关闭
// Thread.sleep(1000 * 10);
// CloseableUtils.closeQuietly(nodeCache);
/*重复监听子节点*/
// PathChildrenCache pathChildrenCache = curatorService.anyListenChildren("/oracle", new PathChildrenCacheListener() {
// public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// if (event.getType() == PathChildrenCacheEvent.Type.CHILD_ADDED
// || event.getType() == PathChildrenCacheEvent.Type.CHILD_UPDATED
// || event.getType() == PathChildrenCacheEvent.Type.CHILD_REMOVED) {
// System.out.println(event.getData().getPath() + ":" + new String(event.getData().getData()));
// } else if (event.getType() == PathChildrenCacheEvent.Type.INITIALIZED) {
// System.out.println("初始化OK");
// } else if (event.getType() == PathChildrenCacheEvent.Type.CONNECTION_RECONNECTED) {
// System.out.println("连接成功");
// }
// }
// });
/**
* 阻塞程序不结束
* 1. 如果创建的是临时节点,这里阻塞;否则程序运行完关闭,临时节点也被删除了
* 2. 监听节点,然后去zkCli.sh里面修改,如果这里不阻塞,程序就结束了
* **/
// System.in.read();
// 关闭连接
CloseableUtils.closeQuietly(curatorService.client);
// 输出连接状态(STOPPED)
System.out.println(curatorService.client.getState());
}
}