前言
上一篇博客中总结了利用curator连接zookeeper,完成对zookeeper中相关节点的CRUD操作,但是zookeeper中令人着迷的还是它的监听机制,这个监听机制可以说是时间服务动态发现与感知的基础。
标准的监听模式
标准的监听模式是使用watcher监听器,这个监听器的定义比较简单,从源码来看Watcher中定义了KeepState和EventType,这两个分别表示通知状态和事件类型。具体可以参见源码
定义一个Watcher也比较简单。
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("触发了watcher事件,节点路径为:" + watchedEvent.getPath() + ",事件类型为:" + watchedEvent.getType());
}
};
这里里面定义了回调的方式,在节点变化的发生指定事件类型的时候,就会回调这个方法,可以针对指定事件类型进行监听。
然后将这个定义的watcher加入到curator的调用链中就可以了。但是这个监听器只能监听一次。
完整实例如下:
public static void testUsingWatcherUpdate(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/watcher";
Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("触发了watcher事件,节点路径为:" + watchedEvent.getPath() + ",事件类型为:" + watchedEvent.getType());
}
};
curatorFramework.getData().usingWatcher(watcher).forPath(nodePath);
System.in.read(); //为了阻塞主线程
}
通过控制台给节点设置新的值之后,控制台会输出如下语句:
但是这个只能监听一次, 如果要多次监听,需要反复注册监听事件。
两次set操作,但是上述控制台只输出了一次日志,所以这个监听机制只能监听一次。
缓存的监听模式
Cache的监听模式,可以理解为利用了一个缓存的机制,同时Cache提供了反复注册的功能。简单来说Cache就是在客户端缓存了指定节点的状态,当感知到ZNode的节点变化的时候,就会触发event事件,注册的监听器就会处理这个事件。Cache的事件有三种类型,Node Cache,Path Cache,Tree Cache。Node Cache是用于ZNode节点的监听,Path Cache是用于ZNode子节点的监听,Tree Cache 相当于前两者的组合。三者的使用方式大同小异:1、新建Cache实例,2、新建监听器,3、将监听器加入到Cache的监听链中。
node cache
这里直接上实例吧
public static void testNodeCacheUpdate(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/watcher";
//1.初始化一个nodeCache
NodeCache nodeCache = new NodeCache(curatorFramework, nodePath, false);
//2.初始化一个NodeCacheListener
NodeCacheListener nodeCacheListener = new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
ChildData childData = nodeCache.getCurrentData();
System.out.println("ZNode 节点的状态变化,path=" + childData.getPath());
System.out.println("ZNode 节点的状态变化,data=" + new String(childData.getData(), "utf-8"));
System.out.println("ZNode 节点的状态变化,stat=" + childData.getStat());
}
};
nodeCache.getListenable().addListener(nodeCacheListener);
/**
* 唯一的一个参数buildInitial代表着是否将该节点的数据立即进行缓存。
* 如果设置为true的话,在start启动时立即调用NodeCache的getCurrentData方法就能够得到对应节点的信息ChildData类,
* 如果设置为false的就得不到对应的信息。
*
* 使用NodeCache来监听节点的事件
*/
nodeCache.start();//start
// 第1次变更节点数据
curatorFramework.setData().forPath(nodePath, "第1次更改内容".getBytes());
Thread.sleep(1000);
// 第2次变更节点数据
curatorFramework.setData().forPath(nodePath, "第2次更改内容".getBytes());
Thread.sleep(1000);
// 第3次变更节点数据
curatorFramework.setData().forPath(nodePath, "第3次更改内容".getBytes());
Thread.sleep(1000);
System.in.read();
// 第4次变更节点数据
// client.delete().forPath(workerPath);
}
可以看到,这里多次修改数据,最终的输出结果也很明显,能够实现多次监听。
path cache
path cache是对指定节点的子节点进行监听,但是对父node以及子节点下的子节点的变化并不会触发相应的监听事件。
/**
* 测试pathChildrenCache
* PathChildrenCache子节点缓存用于子节点的监听,监控本节点的子节点被创建、更新或者删除
* (1)只能监听子节点,监听不到当前节点
* <p>
* (2)不能递归监听,子节点下的子节点不能递归监控
* <p>
* 简单说下Curator的监听原理,无论是PathChildrenCache,还是TreeCache,
* 所谓的监听,都是进行Curator本地缓存视图和ZooKeeper服务器远程的数据节点的对比
* <p>
* 以节点增加事件NODE_ADDED为例,所在本地缓存视图开始的时候,本地视图为空,
* 在数据同步的时候,本地的监听器就能监听到NODE_ADDED事件。
* 这是因为,刚开始本地缓存并没有内容,然后本地缓存和服务器缓存进行对比,发现ZooKeeper服务器有节点而本地缓存没有,
* 这才将服务器的节点缓存到本地,就会触发本地缓存的NODE_ADDED事件。
*
* @param curatorFramework
*/
public static void testPathChildrenCache(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/watcher";
PathChildrenCache pathChildrenCache = new PathChildrenCache(curatorFramework, nodePath, true);
PathChildrenCacheListener pathChildrenCacheListener = new PathChildrenCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {
ChildData childData = pathChildrenCacheEvent.getData();
switch (pathChildrenCacheEvent.getType()) {
case CHILD_ADDED:
System.out.println("子节点增加,path=" + childData.getPath() + ",data=" + new String(childData.getData(), "utf-8"));
break;
case CHILD_UPDATED:
System.out.println("子节点更新,path=" + childData.getPath() + ",data=" + new String(childData.getData(), "utf-8"));
break;
case CHILD_REMOVED:
System.out.println("子节点删除,path=" + childData.getPath() + ",data=" + new String(childData.getData(), "utf-8"));
break;
default:
break;
}
}
};
pathChildrenCache.getListenable().addListener(pathChildrenCacheListener);
pathChildrenCache.start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE);
Thread.sleep(1000);
for (int i = 0; i < 3; i++) {
String childPath = nodePath + "/" + i;
byte[] data = childPath.getBytes();
curatorFramework.create().forPath(childPath, data);
}
Thread.sleep(100000);//主线程睡眠10秒钟,然后再删除子节点,这里依旧会触发子节点变更事件
for (int i = 0; i < 3; i++) {
String childPath = nodePath + "/" + i;
curatorFramework.delete().forPath(childPath);
}
}
运行结果:
tree cache
tree cache看上去像是前两者的结合。具体操作都大同小异
/**
* Tree Cache可以看做是上两种的合体,
* Tree Cache观察的是当前ZNode节点的所有数据。
* 而TreeCache节点树缓存是PathChildrenCache的增强,
* 不光能监听子节点,也能监听节点自身。
*
* @param curatorFramework
*/
public static void testTreeCacheNode(CuratorFramework curatorFramework) throws Exception {
String nodePath = "/watcher";
TreeCache treeCache = new TreeCache(curatorFramework, nodePath);
TreeCacheListener treeCacheListener = new TreeCacheListener() {
@Override
public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {
ChildData data = treeCacheEvent.getData();
switch (treeCacheEvent.getType()) {
case NODE_ADDED:
System.out.println("[TreeNode]节点增加,path="+data.getPath()+",data="+new String(data.getData(),"utf-8"));
break;
case NODE_UPDATED:
System.out.println("[TreeNode]节点更新,path="+data.getPath()+",data="+new String(data.getData(),"utf-8"));
break;
case NODE_REMOVED:
System.out.println("[TreeNode]节点删除,path="+data.getPath()+",data="+new String(data.getData(),"utf-8"));
break;
default:
break;
}
}
};
treeCache.getListenable().addListener(treeCacheListener);
treeCache.start();
Thread.sleep(1000);
for (int i = 0; i < 3; i++) {
String childPath = nodePath + "/" + i;
byte[] data = childPath.getBytes();
curatorFramework.create().creatingParentsIfNeeded().forPath(childPath, data);
}
Thread.sleep(1000);
for (int i = 0; i < 3; i++) {
String childPath = nodePath + "/" + i;
curatorFramework.delete().forPath(childPath);
}
curatorFramework.setData().forPath(nodePath,"update parent".getBytes());
}
运行结果
总结
这篇博客比较偏实例,从某一种程度上来说,只是一些实例的堆积,后续这些监听实例会用到手动RPC的动态发现与感知中,本篇博客也是参考了大牛的博客实例而写的,原博客地址如下:https://blog.csdn.net/crazymakercircle/article/details/85922561