Java原生API操作ZooKeeper可参看:
Java原生API操作Zookeeper(一)
Java原生API操作Zookeeper(二)
相关内容:
基于Curator操作ZooKeeper(一)-基本操作
基于Curator操作ZooKeeper(二)-Watcher操作-补充TreeCache
基于Curator操作ZooKeeper(三)-Curator整合Spring
usingWatcher()方法
在ZooKeeper Watcher监听机制(数据变更的通知)(一)(应用)中介绍过,绑定事件只有三个操作:getData、exists、getChildren。
这个方法有两个重载的方法,实现这两个接口其实都差不多:
使用usingWatcher()方法监听只会触发一次,监听完毕后就会销毁。
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.retry.ExponentialBackoffRetry;
public class CuratorOperator {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.135:2181,192.168.220.136:2181,192.168.220.137:2181";
/**
* 实例化zk客户端
*/
public CuratorOperator() {
/**
* 同步创建zk示例,原生api是异步的
*
* curator链接zookeeper的重试策略:
*
* 1>ExponentialBackoffRetry【推荐】
* baseSleepTimeMs:初始sleep时间(ms)
* maxRetries:最大重试次数,超过时间就不链接了
* maxSleepMs:最大重试时间(ms)
*
* 给定一个初始sleep时间base5leep丁imeMs,在这个基础上结合重试次数,通过以下公式计算出当前需要sleep的时间:
当前sleep时间=baseSleepTimeMs*Math.max(1, random.nextInt(1<<(retryCount+1)))
可以看出,随着重试次数的增加,计算出的sleep时间会越来越大。如果该sleep时间在maxSleepMs的范围之内,那么就使用该sleep时间,否则使用maxSleepMs。另外,
maxRetries参数控制了最大重试次数,以避免无限制的重试。
*/
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
/**
* curator链接zookeeper的策略:
* 2>RetryNTimes【推荐】
* n:重试的次数
* sleepMsBetweenRetries:每次重试间隔的时间(ms)
*/
// RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
/**
* curator链接zookeeper的策略:
* 3>RetryOneTime
* sleepMsBetweenRetry:只重试一次,重试间隔的时间
*/
// RetryPolicy retryPolicy2 = new RetryOneTime(3000);
/**
* 4>
* 永远重试,不推荐使用
*/
// RetryPolicy retryPolicy3 = new RetryForever(retryIntervalMs)
/**
* curator链接zookeeper的策略:
* 5>RetryUntilElapsed
* maxElapsedTimeMs:最大重试时间
* sleepMsBetweenRetries:每次重试间隔
* 重试时间超过maxElapsedTimeMs后,就不再重试
*/
// RetryPolicy retryPolicy4 = new RetryUntilElapsed(2000, 3000);
//创建客户端
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
/**
* CuratorFrameworkFactory工厂在创建出一个客户端CuratorFramework实例之后,实质上并没有完成会话的创建,而是需要调用
CuratorFramework的sta rt)方法来完成会话的创建。
*/
client.start();
}
/**
*
* @Description: 关闭zk客户端连接
*/
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator cto = new CuratorOperator();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
//创建节点
/* byte[] data = "abcd".getBytes();
cto.client.create()
.creatingParentContainersIfNeeded() //递归创建节点
.withMode(CreateMode.PERSISTENT) //节点模式
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) //ACL
.forPath(nodePath,data); //不指定内容,则内容为空*/
//获取节点
/* byte[] bytes = cto.client.getData().forPath(nodePath);
System.out.println("第一次获取节点数据为:"+new String(bytes));
Stat stat = new Stat();
byte[] bytes1 = cto.client.getData().storingStatIn(stat).forPath(nodePath);
System.out.println("第二次获取节点数据为:"+new String(bytes1));
System.out.println("获取的Stat为:"+ JsonUtil.toJSON(stat));
*/
cto.client.getData().usingWatcher((CuratorWatcher) event -> {
System.out.println("触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
}).forPath(nodePath);
//获取子节点
/* List list = cto.client.getChildren().forPath(nodePath);
System.out.println("开始打印子节点:");
list.forEach(result-> System.out.println(result));
System.out.println("打印结束!");*/
//修改节点
/* Stat stat = cto.client.setData().forPath(nodePath,"new1".getBytes());
System.out.println("第一次获取节点数据为:"+new String(cto.client.getData().forPath(nodePath)));
Stat stat1 = cto.client.setData().withVersion(stat.getVersion()).forPath(nodePath, "new2".getBytes());
System.out.println("第二次获取节点数据为:"+new String(cto.client.getData().forPath(nodePath)));*/
//删除节点
/* Stat stat = new Stat();
byte[] bytes1 = cto.client.getData().storingStatIn(stat).forPath(nodePath);
System.out.println("获取节点数据为:"+new String(bytes1));
cto.client.delete()
.guaranteed() //防止网络抖动,只要客户端会话有效,那么Curator 会在后台持续进行删除操作,直到节点删除成功
.deletingChildrenIfNeeded() //如果有子节点会删除,注意除非人为删除namespace,否则namespace不会删除
.withVersion(stat.getVersion())
.forPath(nodePath);*/
Thread.sleep(300000);
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
运行程序后,在客户端执行:
可以看到控制台输出了,可以看出没有输出namespace:
但是再在客户端执行相同的操作,控制台没有输出,说明事件只会触发一次。
ZooKeeper原生支持通过注册Watcher来进行事件监听,但是其使用并不是特别方便,需要开发人员自己反复注册Watcher,比较繁琐。Curator引入了Cache来实现对ZooKeeper服务端事件的监听。Cache是Curator中对事件监听的包装,其对事件的监听其实可以近似看作是一个本地缓存视图和远程ZooKeeper视图的对比过程。同时Curator能够自动为开发人员处理反复注册监听,从而大大简化了原生API开发的繁琐过程。Cache分为两类监听类型:节点监听和子节点监听。
NodeCache的使用
NodeCache有两个构造函数:
//构造NodeCache实例
NodeCache nodeCache = new NodeCache(cto.client,nodePath);
//建立Cache
//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。一般在开发中我们会设置为true。
nodeCache.start();
通过代码测试看看:
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.retry.ExponentialBackoffRetry;
public class CuratorOperator2 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.135:2181,192.168.220.136:2181,192.168.220.137:2181";
public CuratorOperator2() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
client.start();
//client.start(true);
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator2 cto = new CuratorOperator2();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
cto.client.getData().usingWatcher((CuratorWatcher) event -> {
System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
}).forPath(nodePath);
//NodeCache:监听数据节点的变更,会触发事件
//构造NodeCache实例
NodeCache nodeCache = new NodeCache(cto.client,nodePath);
//建立Cache
//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
nodeCache.start();
if(nodeCache.getCurrentData()!=null){
System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
}else {
System.out.println("节点数据为空!");
}
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
运行结果:
测试事件触发:
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.retry.ExponentialBackoffRetry;
public class CuratorOperator2 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";
public CuratorOperator2() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator2 cto = new CuratorOperator2();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
cto.client.getData().usingWatcher((CuratorWatcher) event -> {
System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
}).forPath(nodePath);
//NodeCache:监听数据节点的变更,会触发事件
//构造NodeCache实例
NodeCache nodeCache = new NodeCache(cto.client,nodePath);
//建立Cache
//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
nodeCache.start(true);
if(nodeCache.getCurrentData()!=null){
System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
}else {
System.out.println("节点数据为空!");
}
//添加事件(也有remove),还可以知道Excutor
nodeCache.getListenable().addListener(() -> {
String data = new String(nodeCache.getCurrentData().getData());
System.out.println("节点路径:"+nodeCache.getCurrentData().getPath()+",节点数据为:"+data);
});
System.in.read();
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
运行后在客户端操作:
控制台输出:
如果在客户端执行删除操作:
控制台输出,空指针是因为节点被删除了:
NodeCache不仅可以用于监听数据节点的内容变更,也能监听指定节点是否存在。如果原本节点不存在,那么Cache就会在节点被创建后触发NodeCacheListenera。但是,如果该数据节点被删除,那么Curator就无法触发NodeCacheListener了(后面版本已经修复了这个问题,网上有的资料这么说是有问题的)。
代码测试:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import java.time.LocalDateTime;
public class CuratorOperator2 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";
public CuratorOperator2() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator2 cto = new CuratorOperator2();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
/* cto.client.getData().usingWatcher((CuratorWatcher) event -> {
System.out.println("【使用usingWatcher】触发了watcher事件,节点路径为:"+event.getPath()+",事件类型为:"+event.getType());
}).forPath(nodePath);*/
//NodeCache:监听数据节点的变更,会触发事件
//构造NodeCache实例
NodeCache nodeCache = new NodeCache(cto.client,nodePath);
//建立Cache
//该方法有个boolean类型的参数,默认是false,如果设置为true,那么NodeCache在第一次启动的时候就会立刻从ZooKeeper上读取对应节点的数据内容,并保存在Cache中。
nodeCache.start(true);
if(nodeCache.getCurrentData()!=null){
System.out.println("节点初始化数据为:"+new String(nodeCache.getCurrentData().getData()));
}else {
System.out.println("节点数据为空!");
}
//添加事件(也有remove)
nodeCache.getListenable().addListener(() -> {
/*String data = new String(nodeCache.getCurrentData().getData());
System.out.println("节点路径:"+nodeCache.getCurrentData().getPath()+",节点数据为:"+data);*/
System.out.println(LocalDateTime.now()+" |触发节点事件!!!");
});
System.out.println("开始创建节点!");
//创建节点
cto.client.create()
.creatingParentContainersIfNeeded() //递归创建节点
.withMode(CreateMode.PERSISTENT) //节点模式
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE) //ACL
.forPath(nodePath,"new Time".getBytes());
System.in.read();
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
控制台输出:
在客户端操作:
控制台输出:
PathChildrenCache
有时候我们需要监听某一个节点具体的操作情况,这时候PathChildrenCache就派上用场了。PathChildrenCache用于监听指定ZooKeeper数据节点的子节点变化情况。我们要监听一个节点也没有被删除、新增、修改,我们只需要监听这个节点的父节点即可,即监听这个父节点以下所有的子节点,当某一个子节点发生了增删改操作的时候都会被监听到。
跟NodeCache类似,PathChildrenCache也需要调用start()来初始化:
数据准备:
同步异步测试:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.util.List;
public class CuratorOperator3 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";
public CuratorOperator3() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator3 cto = new CuratorOperator3();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
//为子节点添加watcher
PathChildrenCache childrenCache = new PathChildrenCache(cto.client,nodePath,true);
/**
* StartMode:初始化方式
* NORMAL:普通异步初始化
BUILD_INITIAL_CACHE:同步初始化
POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件
*/
childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
// childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
// childrenCache.start(PathChildrenCache.StartMode.NORMAL);
List list = childrenCache.getCurrentData();
System.out.println("获取子节点列表:");
//如果是BUILD_INITIAL_CACHE可以获取这个数据,如果不是就不行
list.forEach(childData -> {
System.out.println(new String(childData.getData()));
});
System.in.read();
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
注册事件测试:
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.retry.ExponentialBackoffRetry;
import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicInteger;
public class CuratorOperator3 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.220.136:2181,192.168.220.137:2181";
public CuratorOperator3() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 5);
client = CuratorFrameworkFactory.builder() //builder
.connectString(zkServerPath)
.sessionTimeoutMs(10000) //session超时时间
.retryPolicy(retryPolicy) //重试策略
//namespace:
.namespace("testCRUD")
.build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 实例化
CuratorOperator3 cto = new CuratorOperator3();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted ? "连接中" : "已关闭"));
String nodePath = "/dongguabai/a";
//为子节点添加watcher
PathChildrenCache childrenCache = new PathChildrenCache(cto.client,nodePath,true);
/**
* StartMode:初始化方式
* NORMAL:普通异步初始化
BUILD_INITIAL_CACHE:同步初始化
POST_INITIALIZED_EVENT:异步初始化,初始化之后会触发事件,而且所有的子节点的add操作都会来一遍这个也是比较坑的地方
*/
// childrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
childrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);
// childrenCache.start(PathChildrenCache.StartMode.NORMAL);
/*List list = childrenCache.getCurrentData();
System.out.println("获取子节点列表:");
//如果是BUILD_INITIAL_CACHE可以获取这个数据,如果不是就不行
list.forEach(childData -> {
System.out.println(new String(childData.getData()));
});*/
//注册事件,也可以加Excutor
/**
* 当指定节点的子节点发生变化时,就会回调该方法oPathChildrenCacheEvent
类中定义了所有的事件类型,主要包括新增子节点(CHILD_ADDED)、子节点数据
变更(CHILD_UPDATED)和子节点删除(CHILD_REMOVED)三类。
*/
AtomicInteger atomicInteger = new AtomicInteger(0);
childrenCache.getListenable().addListener(((client1, event) -> {
atomicInteger.getAndIncrement();
System.out.println("----- "+LocalDateTime.now()+" "+event.getType());
if(event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)){
System.out.println("子节点初始化成功...");
}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
String path = event.getData().getPath();
System.out.println("添加子节点:" + event.getData().getPath());
System.out.println("子节点数据:" + new String(event.getData().getData()));
}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
System.out.println("删除子节点:" + event.getData().getPath());
}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("修改子节点路径:" + event.getData().getPath());
System.out.println("修改子节点数据:" + new String(event.getData().getData()));
}
//如果想指定节点可以判断路径
/*String path = event.getData().getPath();
if (path.equals(ADD_PATH)) {
System.out.println("添加子节点:" + event.getData().getPath());
System.out.println("子节点数据:" + new String(event.getData().getData()));
} else if (path.equals("/super/imooc/e")) {
System.out.println("添加不正确...");
}*/
}));
Thread.sleep(1000);
System.out.println("结果:"+atomicInteger);
System.in.read();
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("当前客户的状态:" + (isZkCuratorStarted2 ? "连接中" : "已关闭"));
}
}
启动项目,先看控制台:
在客户端执行一些操作:
再看控制台: