Curator的三种缓存。

来源:互联网 发布:ubuntu 查看分区格式 编辑:程序博客网 时间:2024/03/28 22:42

      • Curator分类
      • NodeCache
      • PathChildrenCache
      • TreeCache
      • 示例


在使用原生的ZooKeeper的时候,是可以使用Watcher对节点进行监听的,但是唯一不方便的是一个Watcher只能生效一次,也就是说每次进行监听回调之后我们需要自己重新的设置监听才能达到永久监听的效果。

Curator在这方面做了优化,Curator引入了Cache的概念用来实现对ZooKeeper服务器端进行事件监听。Cache是Curator对事件监听的包装,其对事件的监听可以近似看做是一个本地缓存视图和远程ZooKeeper视图的对比过程。而且Curator会自动的再次监听,我们就不需要自己手动的重复监听了。

ZooKeeper版本:3.4.11,Curator版本:2.12.0


Curator分类。

Curator中的Cache共有三种:NodeCache、PathChildrenCache、TreeCache。

这三种缓存均在:org.apache.curator.framework.recipes.cache 中,该包下的全部内容如下:

这里写图片描述

可以看到都是与这三种有关的类,其中ChildData就是对节点信息的封装,官方API地址。,建议先看官方的API,因为本文不会对缓存中的方法进行详细的介绍,只会介绍其中的大概使用和使用时需要注意的东西。

下面分别对三种Cache进行介绍。


NodeCache。

NodeCache是用来监听节点的数据变化的,当监听的节点的数据发生变化的时候就会回调对应的函数。NodeCache的构造方法有两种:

NodeCache(CuratorFramework client, String path) NodeCache(CuratorFramework client, String path, boolean dataIsCompressed) 

第一个参数就是传入创建的Curator客户端即可,第二个参数就是监听节点的路径,第三个dataIsCompressed参数表示是否对数据进行压缩。

在创建完NodeCache的实例之后,我们需要调用它的start方法才能进行缓存:

void    start()//Start the cache.void    start(boolean buildInitial)//Same as start() but gives the option of doing an initial build

唯一的一个参数代表着是否将该节点的数据立即进行缓存。如果设置为true的话,我们现在调用NodeCache的getCurrentData方法就能够得到对应节点的信息ChildData类,如果设置为false的就得不到对应的信息。

来看下面一段伪代码:

    /**     * 监听节点数据变化     */    public static void nodeCache() {        final NodeCache nodeCache = new NodeCache(client, parentPath, false);        try {            nodeCache.start(false);//true代表缓存当前节点        } catch (Exception e) {            e.printStackTrace();        }        if (nodeCache.getCurrentData() != null) {//只有start中的设置为true才能够直接得到            System.out.println( num++ + ".nodeCache-------CurrentNode Data is:" + new String(nodeCache.getCurrentData().getData()) + "\n===========================\n");//输出当前节点的内容        }        //添加节点数据监听        nodeCache.getListenable().addListener(new NodeCacheListener() {            public void nodeChanged() throws Exception {                System.out.println( num++ + ".nodeCache------节点数据发生了改变,发生的路径为:" + nodeCache.getCurrentData().getPath() + ",节点数据发生了改变 ,新的数据为:" + new String(nodeCache.getCurrentData().getData()) + "\n===========================\n");            }        });    }

如果nodeCache的start使用了true参数,在下面的nodeCache.getCurrentData()得到的才不会是null。

同时在代码中也可以看到对节点进行监听的方法:调用NodeCache的getListenable方法然后使用addListener传入一个NodeCacheListener监听器。每次该节点中的数据发生变化的时候就会执行监听器中的nodeChanged方法。

监听器:

new NodeCacheListener() {            public void nodeChanged() throws Exception {            }        }

使用注意:

如果NodeCache监听的节点为空(也就是说传入的路径不存在)。那么如果我们后面创建了对应的节点,也是会触发事件从而回调nodeChanged方法。但是遗憾的是,删除了该节点并不会触发。


PathChildrenCache。

PathChildrenCache是用来监听指定节点 的子节点变化情况。共有六种构造方法(有两种弃用了,否则就是八种):

PathChildrenCache(CuratorFramework client, String path, boolean cacheData) PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, CloseableExecutorService executorService) PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, ExecutorService executorService) PathChildrenCache(CuratorFramework client, String path, boolean cacheData, boolean dataIsCompressed, ThreadFactory threadFactory) PathChildrenCache(CuratorFramework client, String path, boolean cacheData, ThreadFactory threadFactory) PathChildrenCache(CuratorFramework client, String path, PathChildrenCacheMode mode)

六种常用的也就是第一种,传入Curator端和想要监听的路径,cacheData表示是否将监听变化的节点缓存在其。如果设置为true的话,客户端在接收到节点列表发生变化的同时,也能够获取到节点的数据内容。

同样的在得到PathChildrenCache的实例之后,我们需要调用其start方法才能开始缓存,这里的start方法中可以传入三种模式,也就是API列表中看到的StartMode,其中定义了下面三种枚举:

  1. NORMAL
    The cache will be primed (in the background) with initial values. Events for existing and new nodes will be posted.

  2. BUILD_INITIAL_CACHE
    The cache will be primed (in the foreground) with initial values. PathChildrenCache.rebuild() will be called before the PathChildrenCache.start(StartMode) method returns in order to get an initial view of the node.

  3. POST_INITIALIZED_EVENT
    After cache is primed with initial values (in the background) a PathChildrenCacheEvent.Type.INITIALIZED will be posted.

这三种究竟有什么区别呢?做个试验就好了,假如我们现在有着一个名为Curator-Recipes的父节点,下面有两个子节点:c1、c2。如图所示:

这里写图片描述

如果我们对这个Curator-Recipes进行监听,分别使用三种模式启动方式进行启动,在监听中输出对应的信息:

public class PathCacheTest {    private static final String zkAddress = "centos3";    private static final int sessionTimeout = 2000;    private static String parentPath = "/Curator-Recipes";//父节点    private static CuratorFramework client = CuratorFrameworkFactory.builder()            .connectString(zkAddress)            .sessionTimeoutMs(sessionTimeout)            .retryPolicy(new ExponentialBackoffRetry(1000,3))            .build();    public static void main(String[] args) throws InterruptedException {        client.start();        final PathChildrenCache pathChildrenCache = new PathChildrenCache(client, parentPath, true);        try {            pathChildrenCache.start(PathChildrenCache.StartMode.NORMAL);//启动模式        } catch (Exception e) {            e.printStackTrace();        }        //添加监听        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {            if (pathChildrenCacheEvent.getType() == PathChildrenCacheEvent.Type.INITIALIZED) {                    System.out.println("初始化!");                }                System.out.println("pathChildrenCache------发生的节点变化类型为:" + pathChildrenCacheEvent.getType() + ",发生变化的节点内容为:" + new String(pathChildrenCacheEvent.getData().getData()) + ",路径:" + pathChildrenCacheEvent.getData().getPath() + "\n======================\n");            }        });        Thread.sleep(Integer.MAX_VALUE);    }}

也就是监听函数中输出节点的变化类型、变化内容、对应的路径。

如果是NORMAL启动模式的话,会输出如下内容:

pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:c1,路径:/Curator-Recipes/c1======================pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:c2,路径:/Curator-Recipes/c2======================

可以看到将Curator-Recipes节点下的子节点全部输出了。

如果是POST_INITIALIZED_EVENT模式的话,输出以下内容:

pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:c1,路径:/Curator-Recipes/c1======================pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:c2,路径:/Curator-Recipes/c2======================初始化!

发现触发了INITIALIZED类型的事件,其他的与NORMAL一致。

如果是BUILD_INITIAL_CACHE模式的话,什么都不会输出。在官方解释中说是因为这种模式会在start执行执行之前先执行rebuild的方法,而rebuild的方法不会发出任何事件通知。

当我们其中完成之后就可以对其进行监听了,当节点下的子节点发生删除、增加、更新时都会触发监听器。

监听器:

new PathChildrenCacheListener() {            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {            }        }

需要注意的地方有3点:

  • 如果指定的监听节点不存在的话,会自动的创建该节点。
  • 如果在start之后立马调用getCurrentData()的话是会得到null的,因为此时还没有缓存好内容,如果等待一会在调用getCurrentData()的话就能够得到数据。
  • 如果是POST_INITIALIZED_EVENT模式启动的话,在回调函数中使用PathChildrenCacheEvent.getData()的时候会得到null对象,如果对null调用其他方法比如getPath,但是此时并不会出现异常(抛出了异常,但是控制台不会显示),而是下面的内容均不会输出。

TreeCache

TreeCache有点像上面两种Cache的结合体,NodeCache能够监听自身节点的数据变化(或者是创建该节点),PathChildrenCache能够监听自身节点下的子节点的变化,而TreeCache既能够监听自身节点的变化、也能够监听子节点的变化。

TreeCache的话只有一种构造方法了:

TreeCache(CuratorFramework client, String path)//Create a TreeCache for the given client and path with default options.

与上面的PathChildrenCache不同的是,如果指定的节点路径不存在的话,不会自动创建。但是也能够监听到一个INITIALIZED类型的事件。

TreeCache可以添加一个节点变化的监听器,同样的也可以添加一个异常的监听器。

假如ZooKeeper服务器中还是有如下的节点内容:

这里写图片描述

使用如下代码进行测试,就是当节点的内容发生变化时,输出节点的变化类型和变化的节点路径。

public class TreeCacheTest {    private static final String zkAddress = "centos3";    private static final int sessionTimeout = 2000;    private static String parentPath = "/Curator-Recipes1";//父节点    private static CuratorFramework client = CuratorFrameworkFactory.builder()            .connectString(zkAddress)            .sessionTimeoutMs(sessionTimeout)            .retryPolicy(new ExponentialBackoffRetry(1000,3))            .build();    public static void main(String[] args) throws InterruptedException {        client.start();        final TreeCache treeCache = new TreeCache(client, parentPath);        try {            treeCache.start();        } catch (Exception e) {            e.printStackTrace();        }        //添加错误监听器        treeCache.getUnhandledErrorListenable().addListener(new UnhandledErrorListener() {            public void unhandledError(String s, Throwable throwable) {                System.out.println(".错误原因:" + throwable.getMessage() + "\n==============\n");            }        });        //节点变化的监听器        treeCache.getListenable().addListener(new TreeCacheListener() {            public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {                System.out.println("treeCache ------ Type:" + treeCacheEvent.getType() + ",");                System.out.println(treeCacheEvent.getData().getPath());            }        });        Thread.sleep(Integer.MAX_VALUE);    }}

运行时输出的内容如下:

这里写图片描述

可以看到缓存父节点和子节点的时候触发了NODE_ADDED事件,同时也会触发一个INITIALIZED事件,并且会在异常监听器中发生回调事件,异常的原因就是我们回调函数因为INITIALIZED事件被触发了,但是此时执行第条输出语句的时候发现treeCacheEvent.getData()是null,而我们在null上调用了getPath方法,所以会触发异常监听。

注意:

  • 无论如何都会收到一个INITIALIZED事件的。
  • 无论是TreeCache、PathChildrenCache,所谓的监听都是本地视图和ZooKeeper服务器进行对比。所以如果ZooKeeper节点不为空的话,才会在缓存开始的时候监听到NODE_ADDED事件,这是因为刚开始本地缓存并没有内容,然后本地缓存和服务器缓存进行对比,发现ZooKeeper服务器有节点而本地缓存没有,这才将服务器的节点缓存到本地,所以才会触发NODE_ADDED事件。

示例。

来一个综合性的示例,该示例中先使用init进行初始化操作,会创建一个父节点“/Curator-Recipes”以及父节点下的一个子节点“c1”。然后分别使用TreeCache、PathChildrenCache、NodeCache三种缓存方式进行缓存。最后再创建c2、c3两个节点并进行修改和删除观察监听器的输出内容。

package com.leafage.zk;import org.apache.curator.framework.CuratorFramework;import org.apache.curator.framework.CuratorFrameworkFactory;import org.apache.curator.framework.api.UnhandledErrorListener;import org.apache.curator.framework.recipes.cache.*;import org.apache.curator.retry.ExponentialBackoffRetry;import org.apache.zookeeper.CreateMode;/** * @Author Leafage * @Date 2017/12/6 14:03 **/public class CuratorRecipes {    private static int num = 1;    private static final String zkAddress = "centos3";    private static final int sessionTimeout = 2000;    private static String parentPath = "/Curator-Recipes";//父节点    private static CuratorFramework client = CuratorFrameworkFactory.builder()            .connectString(zkAddress)            .sessionTimeoutMs(sessionTimeout)            .retryPolicy(new ExponentialBackoffRetry(1000,3))            .build();    public static void main(String[] args) throws InterruptedException {        init();        treeCache();        pathChildrenCache();        nodeCache();        testData();        Thread.sleep(Integer.MAX_VALUE);    }    /**     * 初始化操作,创建父节点     */    public static void init() {        client.start();        try {            //可以用用来确保父节点存在,2.9之后弃用//            EnsurePath ensurePath = new EnsurePath(parentPath);//            ensurePath.ensure(client.getZookeeperClient());            if (client.checkExists().forPath(parentPath) == null) {                client.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(parentPath, "This is Parent Data!".getBytes());            }            client.create().withMode(CreateMode.EPHEMERAL).forPath(parentPath + "/c1","This is C1.".getBytes());//创建第一个节点        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 监听子节点变化     */    public static void pathChildrenCache() {        final PathChildrenCache pathChildrenCache = new PathChildrenCache(client, parentPath, true);        try {            pathChildrenCache.start(PathChildrenCache.StartMode.POST_INITIALIZED_EVENT);//启动模式        } catch (Exception e) {            e.printStackTrace();        }        //添加监听        pathChildrenCache.getListenable().addListener(new PathChildrenCacheListener() {            public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception {                System.out.println( num++ + ".pathChildrenCache------发生的节点变化类型为:" + pathChildrenCacheEvent.getType() + ",发生变化的节点内容为:" + new String(pathChildrenCacheEvent.getData().getData()) + "\n======================\n");            }        });    }    /**     * 监听节点数据变化     */    public static void nodeCache() {        final NodeCache nodeCache = new NodeCache(client, parentPath, false);        try {            nodeCache.start(true);//true代表缓存当前节点        } catch (Exception e) {            e.printStackTrace();        }        if (nodeCache.getCurrentData() != null) {//只有start中的设置为true才能够直接得到            System.out.println( num++ + ".nodeCache-------CurrentNode Data is:" + new String(nodeCache.getCurrentData().getData()) + "\n===========================\n");//输出当前节点的内容        }        //添加节点数据监听        nodeCache.getListenable().addListener(new NodeCacheListener() {            public void nodeChanged() throws Exception {                System.out.println( num++ + ".nodeCache------节点数据发生了改变,发生的路径为:" + nodeCache.getCurrentData().getPath() + ",节点数据发生了改变 ,新的数据为:" + new String(nodeCache.getCurrentData().getData()) + "\n===========================\n");            }        });    }    /**     * 同时监听数据变化和子节点变化     */    public static void treeCache() {        final TreeCache treeCache = new TreeCache(client, parentPath);        try {            treeCache.start();        } catch (Exception e) {            e.printStackTrace();        }        //添加错误        treeCache.getUnhandledErrorListenable().addListener(new UnhandledErrorListener() {            public void unhandledError(String s, Throwable throwable) {                System.out.println(num++ + ".错误原因:" + throwable.getMessage() + "\n==============\n");            }        });        treeCache.getListenable().addListener(new TreeCacheListener() {            public void childEvent(CuratorFramework curatorFramework, TreeCacheEvent treeCacheEvent) throws Exception {                System.out.println( num++ + ".treeCache------当前发生的变化类型为:" +  treeCacheEvent.getType() + ",发生变化的节点内容为:" + new String(treeCacheEvent.getData().getData()) + "\n=====================\n");            }        });    }    /**     * 创建节点、修改数据、删除节点等操作,用来给其他的监听器测试使用     */    public static void testData() {        try {            client.create().withMode(CreateMode.EPHEMERAL).forPath(parentPath + "/c2","This is C2.".getBytes());//创建第一个节点            client.create().withMode(CreateMode.EPHEMERAL).forPath(parentPath + "/c3","This is C3.".getBytes());//创建第一个节点            client.setData().forPath(parentPath + "/c2", "This is New C2.".getBytes());//修改节点数据            client.delete().forPath(parentPath + "/c3");//删除一个节点            client.delete().deletingChildrenIfNeeded().forPath(parentPath);//将父节点下所有内容删除        } catch (Exception e) {            e.printStackTrace();        }    }}

输出内容(每次运行结果输出顺序可能不一致):

1.treeCache------当前发生的变化类型为:NODE_ADDED,发生变化的节点内容为:This is Parent Data!=====================2.treeCache------当前发生的变化类型为:NODE_ADDED,发生变化的节点内容为:This is C1.=====================4.错误原因:null==============5.nodeCache-------CurrentNode Data is:This is Parent Data!===========================6.pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:This is C1.======================8.nodeCache------节点数据发生了改变,发生的路径为:/Curator-Recipes,节点数据发生了改变 ,新的数据为:This is Parent Data!===========================9.treeCache------当前发生的变化类型为:NODE_ADDED,发生变化的节点内容为:This is C2.=====================10.treeCache------当前发生的变化类型为:NODE_ADDED,发生变化的节点内容为:This is C3.=====================11.pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:This is C3.======================12.pathChildrenCache------发生的节点变化类型为:CHILD_ADDED,发生变化的节点内容为:This is C2.======================13.treeCache------当前发生的变化类型为:NODE_UPDATED,发生变化的节点内容为:This is New C2.=====================14.pathChildrenCache------发生的节点变化类型为:CHILD_UPDATED,发生变化的节点内容为:This is New C2.======================15.pathChildrenCache------发生的节点变化类型为:CHILD_REMOVED,发生变化的节点内容为:This is C3.======================16.treeCache------当前发生的变化类型为:NODE_REMOVED,发生变化的节点内容为:This is C3.=====================17.treeCache------当前发生的变化类型为:NODE_REMOVED,发生变化的节点内容为:This is C1.=====================18.pathChildrenCache------发生的节点变化类型为:CHILD_REMOVED,发生变化的节点内容为:This is C1.======================19.pathChildrenCache------发生的节点变化类型为:CHILD_REMOVED,发生变化的节点内容为:This is New C2.======================20.treeCache------当前发生的变化类型为:NODE_REMOVED,发生变化的节点内容为:This is New C2.=====================21.treeCache------当前发生的变化类型为:NODE_REMOVED,发生变化的节点内容为:This is Parent Data!=====================

从输出内容可以看到:

1、2 :代表是TreeCache监听到了父节点和c1节点的创建缓存事件。

3、4 :同时会发现并没有3这条语句,而是直接跳到了4,这是因为接收到的事件为:INITIALIZED,所以使用getData会得到null,而我们试图在null上调用getPath,所以才会触发异常。

5:NodeCache缓存进行start的时候传入true参数,所以能够直接得到当前节点的内容。

6: PathChildrenCache缓存成功c1的时候接收到的事件。

7:会发现没有7,因为PathChildrenCache的启动模式是:INITIALIZED,此时也是试图在null上调用GetPath,但是PathChildrenCache没有提供异常监听器,所以没办法获取。

8:第八点最让人疑惑了,因为上面的代码中并没有对父节点的数据进行改变,但是却监听到了这个事件,做了很多的测试发现,触发这个事件的原因为后面的testData方法中调用create导致的,并且只会监听到一次,这一点的具体原因还不太清楚。

9、10、11、12:创建c2、c3节点是TreeCache和PathChildrenCache监听到的事件。

13、14:修改c2节点数据,TreeCache和PathChildrenCache监听到的事件。

15、16、17、18、19、20:删除c2、c1、c3节点时,TreeCache和PathChildrenCache监听到的事件。

21:删除根节点时接收到的监听事件,此时只有TreeCache能够监听到。

原创粉丝点击