Zookeeper watcher监听用法

zookeeper本质上是一个文件系统,目录、文件节点的创建有一定规则,这些规则应用分布式系统中,协调各节点之间的服务,常用的用法有服务发现、服务注册、服务通知等。

本文讲解监听api的用法,如何收到操作节点的通知,使用的客户端是java版本。

熟悉zookeeper客户端api的应该知道,客户端有以下几中方法,不用记住,一看便知。

    1. create,创建节点,根据持久化规则,分为永久节点和临时节点。
    1. setData,更新节点,修改节点存储的内容。
    1. getData,读取节点,读取节点存储的内容。
    1. delete,删除节点。
    1. getChildren,读取子节点内容。

创建zk客户端代码,监控节点的变化。

            ZooKeeper zk = new ZooKeeper(hostPort, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event.getType() == null) {
                        return;
                    }
                    System.out.println(String.format("触发事件: [%s], Path: [%s]", event.getType(), event.getPath()));
                }
            });

创建节点:

    private static void createNode() throws KeeperException, InterruptedException {
        zk.create("/tmp_root_path", "It is root path of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zk.create("/tmp_root_path/childPath1", "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zk.create("/tmp_root_path/childPath2", "It is the secondary node of tmp_root_path".getBytes(),  ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

执行后,会发现,客户端watcher回调并没有收到[NodeCreated]事件,这是怎么回事呢?
不妨在创建节点前,先判断节点是否存在,增强程序的健壮性。

    private static void createNodeIfNotExist() throws KeeperException, InterruptedException {
        String tmpRootPath = "/tmp_root_path";
        if (isNodeExist(tmpRootPath)) {
            System.out.println(String.format("node[%s] existed", tmpRootPath));
        } else {
            zk.create(tmpRootPath, "It is root path of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        String secondChildPath1 = "/tmp_root_path/childPath1";
        if (isNodeExist(secondChildPath1)) {
            System.out.println(String.format("node[%s] existed", secondChildPath1));
        } else {
            zk.create(secondChildPath1, "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        String secondChildPath2 = "/tmp_root_path/childPath2";
        if (isNodeExist(secondChildPath2)) {
            System.out.println(String.format("node[%s] existed", secondChildPath2));
        } else {
            zk.create(secondChildPath2, "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }
    private static boolean isNodeExist(String nodePath) {
        try {
            return zk.exists(nodePath, true) != null;
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

再次运行,有惊喜,触发通知如下:

触发事件: [NodeCreated], Path: [/tmp_root_path]
触发事件: [NodeCreated], Path: [/tmp_root_path/childPath1]
触发事件: [NodeCreated], Path: [/tmp_root_path/childPath2]

很明显,三次创建节点的通知都受到了,可见,创建节点前判空方法exist是决定是否得到通知的关键,下面看exist方法,第一个参数是节点路径,第二参数就是通知客户端的关键,设置为false,发现创建节点并不会更新了。

    private static boolean isNodeExist(String nodePath) {
        try {
            return zk.exists(nodePath, false) != null;
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }

更新节点

调用setData方法更新节点内容,发现并没有收到更新通知。

            zk.setData("/tmp_root_path/childPath2", "The second time updating: Child2".getBytes(), -1);

更新节点,先读取节点内容看看,有没有惊喜。

            System.out.println(new String(zk.getData("/tmp_root_path/childPath2", true, null)));
            zk.setData("/tmp_root_path/childPath2", "The second time updating: Child2".getBytes(), -1);

执行后发现,真的有惊喜,有打印客户端通知更新事件

触发事件: [NodeDataChanged], Path: [/tmp_root_path/childPath2]

同样的,getData的第二个参数是收到通知的关键,设置为false,同样收不到更新通知。

删除节点

调用delete方法删除节点,发现并没有收到删除通知。

            zk.delete("/tmp_root_path/childPath1", -1);

同理,可推断,应该是操作节点前需要设置通知节点的动作,前置getData和isExist方法,就能收到我们想要的删除通知。

            isNodeExist("/tmp_root_path/childPath1");
            System.out.println(new String(zk.getData("/tmp_root_path/childPath1", true, null)));
            zk.delete("/tmp_root_path/childPath1", -1);

先判断节点,再读取节点,发现只会收到一次删除通知,说明重复的watcher,zk通知时已经做了过滤。

It is the secondary node of tmp_root_path
触发事件: [NodeDeleted], Path: [/tmp_root_path/childPath1]

另外,如果删除根目录/tmp_root_path,其子目录中任然存在节点程序会报错:

org.apache.zookeeper.KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /tmp_root_path
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:128)
    at org.apache.zookeeper.KeeperException.create(KeeperException.java:54)
    at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:882)
    at org.wangep.websocket.dubbo.zk.TestZkWatcher.main(TestZkWatcher.java:100)

因此删除目录节点前,需要判空处理,使用getChildren方法:

            List childrenList = zk.getChildren("/tmp_root_path", true);
            if (childrenList == null || childrenList.size() <= 0) {
                zk.delete("/tmp_root_path", -1);
            }

最后,最后一个带watcher的方法闪亮登场,第二个参数决定通知客户端。

总结

由上述测试可知,zk中创建、更新、删除节点前必须前置isExist、getData、getChildren方法,方法的第二个参数设置成true,只会生效一次。 getChildren()方法仅仅监控对应节点直接子目录的一次变化,但是只会监控直接子节点的增减情况,不会监控数据变化情况!若要每次对应节点发生增减变化都被监测到,那么每次都得先调用getChildren()方法获取一遍节点的子节点列表!

下面给出测试的所有代码提供参考:


public class TestZkWatcher {

    static ZooKeeper zk = null;


    private static boolean isNodeExist(String nodePath) {
        try {
            return zk.exists(nodePath, true) != null;
        } catch (KeeperException | InterruptedException e) {
            e.printStackTrace();
        }
        return false;
    }
    

    private static void createNode() throws KeeperException, InterruptedException {
        zk.create("/tmp_root_path", "It is root path of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zk.create("/tmp_root_path/childPath1", "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        zk.create("/tmp_root_path/childPath2", "It is the secondary node of tmp_root_path".getBytes(),  ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    }

    private static void createNodeIfNotExist() throws KeeperException, InterruptedException {
        String tmpRootPath = "/tmp_root_path";
        if (isNodeExist(tmpRootPath)) {
            System.out.println(String.format("node[%s] existed", tmpRootPath));
        } else {
            zk.create(tmpRootPath, "It is root path of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        String secondChildPath1 = "/tmp_root_path/childPath1";
        if (isNodeExist(secondChildPath1)) {
            System.out.println(String.format("node[%s] existed", secondChildPath1));
        } else {
            zk.create(secondChildPath1, "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }

        String secondChildPath2 = "/tmp_root_path/childPath2";
        if (isNodeExist(secondChildPath2)) {
            System.out.println(String.format("node[%s] existed", secondChildPath2));
        } else {
            zk.create(secondChildPath2, "It is the secondary node of tmp_root_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        }
    }


    public static void main(String[] args) {

        try {
            System.out.println("Starting to connect Zk......");
            String hostPort = "localhost:2181";
            int sessionTimeout = 5000;

            zk = new ZooKeeper(hostPort, sessionTimeout, new Watcher() {
                @Override
                public void process(WatchedEvent event) {
                    if (event.getType() == null) {
                        return;
                    }
                    System.out.println(String.format("触发事件: [%s], Path: [%s]", event.getType(), event.getPath()));
                }
            });

            System.out.println("Zookeeper connected");

            TimeUnit.SECONDS.sleep(1);

            createNodeIfNotExist();


//            System.out.println(new String(zk.getData("/tmp_root_path/childPath2", true, null)));
            isNodeExist("/tmp_root_path/childPath2");
            zk.setData("/tmp_root_path/childPath2", "The second time updating: Child2".getBytes(), -1);

            isNodeExist("/tmp_root_path/childPath1");
            System.out.println(new String(zk.getData("/tmp_root_path/childPath1", true, null)));
            zk.delete("/tmp_root_path/childPath1", -1);
            zk.delete("/tmp_root_path/childPath2", -1);

            List childrenList = zk.getChildren("/tmp_root_path", true);
            if (childrenList == null || childrenList.size() <= 0) {
                zk.delete("/tmp_root_path", -1);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

你可能感兴趣的:(Zookeeper watcher监听用法)