curator Cache实现

137 阅读2分钟

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事件响应过程如下

nodecache.png

当接受到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类在设计上采用了命令模式,内置一个消息队列和处理消息的线程池,用于异步执行命令

offerOperation.png

在理解这一点之后,只需要了解每个命令都做了什么,就知道PathChildrenCache是如何实现的。而PathChildrenCache定义了三种命令GetDataOperation,EventOperationRefreshOperation,但实际上命令类并没有具体实现命令,实现还是在PathChildrenCache中,比如RefreshOperation

@Override
public void invoke() throws Exception
{
    cache.refresh(mode);
}

childrenCache.start();启动过程为例,首先先来看refresh命令的实现

refresh.png

get data命令执行过程如下。与refresh不同,get data watch每一个子节点,而refresh只watch父节点,也就是示例代码中的/sxm/children

getData.png

这里再单独画一下data watcher的逻辑,所有子节点变动都会被data watcher处理。

dataWatcher.png

最后是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初始化完成事件


参考文献:
[1]:Recipes
[2]:command