zookeeper(二)——curator中的watcher机制

前言

上一篇博客中总结了利用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

发布了129 篇原创文章 · 获赞 37 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/liman65727/article/details/101097593