Curator中watch实现
Curator Cache 包提供了节点缓存的功能,除此之外还可以监听缓存的数,对update/create/delete事件进行响应。
NodeCache
实例用法如下
final CuratorFramework zkClient = getClient();
final String path = "/sxm/nodecache";
zkClient.start();
zkClient.create().creatingParentsIfNeeded().forPath(path);
// nodeCache
final NodeCache nodeCache = new NodeCache(zkClient, path);
nodeCache.getListenable().addListener(new NodeCacheListener() {
@Override
public void nodeChanged() throws Exception {
Stat stat = zkClient.checkExists().forPath(path);
if(stat != null)
System.out.println("change happen:" + new String(nodeCache.getCurrentData().getData()));
else
System.out.println("path has been removed");
}
});
nodeCache.start();
zkClient.setData().forPath(path, "first".getBytes());
Thread.sleep(5000);
byte[] firstBetys = zkClient.getData().forPath(path);
System.out.println("next:"+new String(firstBetys));
zkClient.delete().forPath(path);
Thread.sleep(5000);
nodeCache.close();
NodeCache在应用的时候,有四个关键步骤
nodeCache.getListenable().addListener
nodeCache.start()
nodeCache.close()
NodeCache
对zk事件响应过程如下
当接受到zk事件后,自动重复注册watch到path上,开发者无需关注watch注册逻辑。获取到服务端最新数据后,与本地数据对比,如果有变化则通知所有listener。
在整个流程中,check exist和get data都using watch,这里是不是重复注册呢? 这里涉及到zk客户端对watch的原生实现原理。这里所谓的重复注册实际是两种不同watch类型,在类org.apache.zookeeper.ZooKeeper.ZKWatchManager
中可以可以看到
private final Map<String, Set<Watcher>> dataWatches = new HashMap();
private final Map<String, Set<Watcher>> existWatches = new HashMap();
private final Map<String, Set<Watcher>> childWatches = new HashMap();
PathChildrenCache
用例
final CuratorFramework zkClient = getClient();
final String path = "/sxm/children";
zkClient.start();
zkClient.create().creatingParentsIfNeeded().forPath(path);
PathChildrenCache childrenCache = new PathChildrenCache(zkClient, path, true);
childrenCache.getListenable().addListener((client, event) -> System.out.println("type is "+event.getType()+" at path is "+event.getData().getPath()));
childrenCache.start();
while(true) {
Thread.sleep(5000);
}
PathChildrenCache
类在设计上采用了命令模式,内置一个消息队列和处理消息的线程池,用于异步执行命令
在理解这一点之后,只需要了解每个命令都做了什么,就知道PathChildrenCache
是如何实现的。而PathChildrenCache
定义了三种命令GetDataOperation
,EventOperation
和RefreshOperation
,但实际上命令类并没有具体实现命令,实现还是在PathChildrenCache
中,比如RefreshOperation
中
@Override
public void invoke() throws Exception
{
cache.refresh(mode);
}
以childrenCache.start();
启动过程为例,首先先来看refresh命令的实现
get data命令执行过程如下。与refresh不同,get data watch每一个子节点,而refresh只watch父节点,也就是示例代码中的/sxm/children
这里再单独画一下data watcher的逻辑,所有子节点变动都会被data watcher处理。
最后是event命令,相对简单,就是将事件传递到我们添加的监听器中。
void callListeners(final PathChildrenCacheEvent event)
{
listeners.forEach
(
new Function<PathChildrenCacheListener, Void>()
{
@Override
public Void apply(PathChildrenCacheListener listener)
{
try
{
listener.childEvent(client, event);
}
catch ( Exception e )
{
ThreadUtils.checkInterrupted(e);
handleException(e);
}
return null;
}
}
);
}
此外还有两种启动模式
BUILD_INITIAL_CACHE-在start方法返回前,初始化获取每个子节点数据并缓存 POST_INITIALIZED_EVENT-在后台异步初始化数据完成后,会发送一个INITIALIZED初始化完成事件