ZooKeeper Watch Java API浅析getChildren

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介:         Watch是ZooKeeper中非常重要的一个机制,它可以监控ZooKeeper中节点的变化情况,告知客户端。下面,我们以代码为例来分析Watch在ZooKeeper中是如何实现的。

        Watch是ZooKeeper中非常重要的一个机制,它可以监控ZooKeeper中节点的变化情况,告知客户端。下面,我们以代码为例来分析Watch在ZooKeeper中是如何实现的。ZooKeeper中一共由三种方法可以实现Watch,分别为getData、exists和getChildren,今天我们先来看下getChildren()方法:

        3、getChildren

import java.io.IOException;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.data.Stat;

public class TestZooKeeperWatcher {

	public static void main(String[] args) {

		ZooKeeper zk = null;
		try {

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

			System.out.println("开始连接ZooKeeper...");

			// 创建与ZooKeeper服务器的连接zk
			String address = "192.168.1.226:2181";
			int sessionTimeout = 3000;
			zk = new ZooKeeper(address, sessionTimeout, new Watcher() {
				// 监控所有被触发的事件
				public void process(WatchedEvent event) {
					if (event.getType() == null || "".equals(event.getType())) {
						return;
					}
					System.out.println("已经触发了" + event.getType() + "事件!");
				}
			});

			System.out.println("ZooKeeper连接创建成功!");

			Thread.currentThread().sleep(1000l);

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

			// 创建根目录节点
			// 路径为/tmp_root_path
			// 节点内容为字符串"我是根目录/tmp_root_path"
			// 创建模式为CreateMode.PERSISTENT
			System.out.println("开始创建根目录节点/tmp_root_path...");
			zk.create("/tmp_root_path", "我是根目录/tmp_root_path".getBytes(),
					Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			System.out.println("根目录节点/tmp_root_path创建成功!");

			Thread.currentThread().sleep(1000l);

			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			
			// 获取子目录节点列表
			System.out.println("开始获取根目录/tmp_root_path节点的子目录节点列...");
			System.out.println(zk.getChildren("/tmp_root_path", true));
			System.out.println("根目录/tmp_root_path节点的子目录节点列获取成功!");
			
			Thread.currentThread().sleep(1000l);

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

			// 创建第一个子目录节点
			// 路径为/tmp_root_path/childPath1
			// 节点内容为字符串"我是第一个子目录/tmp_root_path/childPath1"
			// 创建模式为CreateMode.PERSISTENT
			System.out.println("开始创建第一个子目录节点/tmp_root_path/childPath1...");
			zk.create("/tmp_root_path/childPath1",
					"我是第一个子目录/tmp_root_path/childPath1".getBytes(),
					Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			System.out.println("第一个子目录节点/tmp_root_path/childPath1创建成功!");

			Thread.currentThread().sleep(1000l);

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

			// 创建第二个子目录节点
			// 路径为/tmp_root_path/childPath2
			// 节点内容为字符串"我是第二个子目录/tmp_root_path/childPath2"
			// 创建模式为CreateMode.PERSISTENT
			System.out.println("开始创建第二个子目录节点/tmp_root_path/childPath2...");
			zk.create("/tmp_root_path/childPath2",
					"我是第二个子目录/tmp_root_path/childPath2".getBytes(),
					Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
			System.out.println("第二个子目录节点/tmp_root_path/childPath2创建成功!");

			Thread.currentThread().sleep(1000l);

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

			// 修改第一个子目录节点/tmp_root_path/childPath1数据
			System.out.println("开始修改第一个子目录节点/tmp_root_path/childPath1数据...");
			zk.setData("/tmp_root_path/childPath1",
					"我是修改数据后的第一个子目录/tmp_root_path/childPath1".getBytes(), -1);
			System.out.println("修改第一个子目录节点/tmp_root_path/childPath1数据成功!");

			Thread.currentThread().sleep(1000l);

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

			// 修改第二个子目录节点/tmp_root_path/childPath2数据
			System.out.println("开始修改第二个子目录节点/tmp_root_path/childPath2数据...");
			zk.setData("/tmp_root_path/childPath2",
					"我是修改数据后的第二个子目录/tmp_root_path/childPath2".getBytes(), -1);
			System.out.println("修改第二个子目录节点/tmp_root_path/childPath2数据成功!");

			Thread.currentThread().sleep(1000l);

			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			
			// 修改根目录节点数据
			System.out.println("开始修改根目录节点/tmp_root_path数据...");
			zk.setData("/tmp_root_path",
					"我是修改数据后的根目录/tmp_root_path".getBytes(), -1);
			System.out.println("修改根目录节点/tmp_root_path数据成功!");
			
			Thread.currentThread().sleep(1000l);
			
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			
			// 删除第一个子目录节点
			System.out.println("开始删除第一个子目录节点/tmp_root_path/childPath1...");
			zk.delete("/tmp_root_path/childPath1", -1);
			System.out.println("第一个子目录节点/tmp_root_path/childPath1删除成功!");

			Thread.currentThread().sleep(1000l);

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

			// 删除第二个子目录节点
			System.out.println("开始删除第二个子目录节点/tmp_root_path/childPath2...");
			zk.delete("/tmp_root_path/childPath2", -1);
			System.out.println("第二个子目录节点/tmp_root_path/childPath2删除成功!");

			Thread.currentThread().sleep(1000l);

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

			// 删除根目录节点
			System.out.println("开始删除根目录节点/tmp_root_path...");
			zk.delete("/tmp_root_path", -1);
			System.out.println("根目录节点/tmp_root_path删除成功!");

			Thread.currentThread().sleep(1000l);

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

		} catch (IOException | KeeperException | InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			// 关闭连接
			if (zk != null) {
				try {
					zk.close();
					System.out.println("释放ZooKeeper连接成功!");

				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}

	}
}
        执行结果如下:

...
...
...
...
开始连接ZooKeeper...
ZooKeeper连接创建成功!
已经触发了None事件!
...
...
...
...
开始创建根目录节点/tmp_root_path...
根目录节点/tmp_root_path创建成功!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始创建第一个子目录节点/tmp_root_path/childPath1...
第一个子目录节点/tmp_root_path/childPath1创建成功!
已经触发了NodeChildrenChanged事件!
...
...
...
...
开始创建第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2创建成功!
...
...
...
...
开始修改第一个子目录节点/tmp_root_path/childPath1数据...
修改第一个子目录节点/tmp_root_path/childPath1数据成功!
...
...
...
...
开始修改第二个子目录节点/tmp_root_path/childPath2数据...
修改第二个子目录节点/tmp_root_path/childPath2数据成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始删除第一个子目录节点/tmp_root_path/childPath1...
第一个子目录节点/tmp_root_path/childPath1删除成功!
...
...
...
...
开始删除第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2删除成功!
...
...
...
...
开始删除根目录节点/tmp_root_path...
根目录节点/tmp_root_path删除成功!
...
...
...
...
释放ZooKeeper连接成功!
        而当我们在获取子目录节点列表getChildren()方法调用之后,接着调用修改根目录节点数据的setData()方法,添加代码和结果如下:

			// 修改根目录节点数据
			System.out.println("开始修改根目录节点/tmp_root_path数据...");
			zk.setData("/tmp_root_path",
					"我是修改数据后的根目录/tmp_root_path".getBytes(), -1);
			System.out.println("修改根目录节点/tmp_root_path数据成功!");
			
			Thread.currentThread().sleep(1000l);
			
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
...
...
...
...
开始连接ZooKeeper...
ZooKeeper连接创建成功!
已经触发了None事件!
...
...
...
...
开始创建根目录节点/tmp_root_path...
根目录节点/tmp_root_path创建成功!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始创建第一个子目录节点/tmp_root_path/childPath1...
已经触发了NodeChildrenChanged事件!
第一个子目录节点/tmp_root_path/childPath1创建成功!
...
...
...
...
开始创建第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2创建成功!
...
...
...
...
开始修改第一个子目录节点/tmp_root_path/childPath1数据...
修改第一个子目录节点/tmp_root_path/childPath1数据成功!
...
...
...
...
开始修改第二个子目录节点/tmp_root_path/childPath2数据...
修改第二个子目录节点/tmp_root_path/childPath2数据成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始删除第一个子目录节点/tmp_root_path/childPath1...
第一个子目录节点/tmp_root_path/childPath1删除成功!
...
...
...
...
开始删除第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2删除成功!
...
...
...
...
开始删除根目录节点/tmp_root_path...
根目录节点/tmp_root_path删除成功!
...
...
...
...
释放ZooKeeper连接成功!
        它仍然只是监控根目录下的子节点变化情况,而且触发的是NodeChildrenChanged事件!而当我们在创建第一个子节点后如果再创建它的一个子节点,并且在创建之前还是先获取根目录/tmp_root_path节点的子目录节点列,执行结果会怎么样呢?添加的代码和执行结果如下:

			// 获取子目录节点列表
			System.out.println("开始获取根目录/tmp_root_path节点的子目录节点列...");
			System.out.println(zk.getChildren("/tmp_root_path", true));
			System.out.println("根目录/tmp_root_path节点的子目录节点列获取成功!");

			Thread.currentThread().sleep(1000l);

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

			// 创建第一个子目录节点的子节点
			// 路径为/tmp_root_path/childPath1/childPath1
			// 节点内容为字符串"我是第一个子目录/tmp_root_path/childPath1/childPath1"
			// 创建模式为CreateMode.PERSISTENT
			System.out
					.println("开始创建第一个子目录节点的子节点/tmp_root_path/childPath1/childPath1...");
			zk.create("/tmp_root_path/childPath1/childPath1",
					"我是第一个子目录的子节点/tmp_root_path/childPath1/childPath1"
							.getBytes(), Ids.OPEN_ACL_UNSAFE,
					CreateMode.PERSISTENT);
			System.out
					.println("第一个子目录节点的子节点/tmp_root_path/childPath1/childPath1创建成功!");

			Thread.currentThread().sleep(1000l);

			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
...
...
...
...
开始连接ZooKeeper...
ZooKeeper连接创建成功!
已经触发了None事件!
...
...
...
...
开始创建根目录节点/tmp_root_path...
根目录节点/tmp_root_path创建成功!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始创建第一个子目录节点/tmp_root_path/childPath1...
第一个子目录节点/tmp_root_path/childPath1创建成功!
已经触发了NodeChildrenChanged事件!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[childPath1]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始创建第一个子目录节点的子节点/tmp_root_path/childPath1/childPath1...
第一个子目录节点的子节点/tmp_root_path/childPath1/childPath1创建成功!
...
...
...
...
开始创建第二个子目录节点/tmp_root_path/childPath2...
已经触发了NodeChildrenChanged事件!
第二个子目录节点/tmp_root_path/childPath2创建成功!
...
...
...
...
开始修改第一个子目录节点/tmp_root_path/childPath1数据...
修改第一个子目录节点/tmp_root_path/childPath1数据成功!
...
...
...
...
开始修改第二个子目录节点/tmp_root_path/childPath2数据...
修改第二个子目录节点/tmp_root_path/childPath2数据成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始删除第一个子目录节点/tmp_root_path/childPath1...
org.apache.zookeeper.KeeperException$NotEmptyException: KeeperErrorCode = Directory not empty for /tmp_root_path/childPath1
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:125)
	at org.apache.zookeeper.KeeperException.create(KeeperException.java:51)
	at org.apache.zookeeper.ZooKeeper.delete(ZooKeeper.java:873)
	at com.jngreen.bgm.scheduler.TestZooKeeperWatcher.main(TestZooKeeperWatcher.java:197)
释放ZooKeeper连接成功!
        还是只会监控直接子目录下的节点,在增加第二个节点时触发NodeChildrenChanged事件,并不会越级监控!当然,出现org.apache.zookeeper.KeeperException$NotEmptyException异常是因为我们删除第一个节点时,由于其还有节点,所以才会报错!

        还有一件有意思的事情,当我们在修改第一个子节点数据前获取根目录/tmp_root_path节点的子目录节点列表,调用getChildren()方法,结果会怎样呢?添加处的代码和执行结果如下:

			// 获取子目录节点列表
			System.out.println("开始获取根目录/tmp_root_path节点的子目录节点列...");
			System.out.println(zk.getChildren("/tmp_root_path", true));
			System.out.println("根目录/tmp_root_path节点的子目录节点列获取成功!");

			Thread.currentThread().sleep(1000l);

			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			
			// 修改第一个子目录节点/tmp_root_path/childPath1数据
			System.out.println("开始修改第一个子目录节点/tmp_root_path/childPath1数据...");
			zk.setData("/tmp_root_path/childPath1",
					"我是修改数据后的第一个子目录/tmp_root_path/childPath1".getBytes(), -1);
			System.out.println("修改第一个子目录节点/tmp_root_path/childPath1数据成功!");

			Thread.currentThread().sleep(1000l);

			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
			System.out.println("...");
...
...
...
...
开始连接ZooKeeper...
ZooKeeper连接创建成功!
已经触发了None事件!
...
...
...
...
开始创建根目录节点/tmp_root_path...
根目录节点/tmp_root_path创建成功!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始创建第一个子目录节点/tmp_root_path/childPath1...
已经触发了NodeChildrenChanged事件!
第一个子目录节点/tmp_root_path/childPath1创建成功!
...
...
...
...
开始创建第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2创建成功!
...
...
...
...
开始获取根目录/tmp_root_path节点的子目录节点列...
[childPath2, childPath1]
根目录/tmp_root_path节点的子目录节点列获取成功!
...
...
...
...
开始修改第一个子目录节点/tmp_root_path/childPath1数据...
修改第一个子目录节点/tmp_root_path/childPath1数据成功!
...
...
...
...
开始修改第二个子目录节点/tmp_root_path/childPath2数据...
修改第二个子目录节点/tmp_root_path/childPath2数据成功!
...
...
...
...
开始修改根目录节点/tmp_root_path数据...
修改根目录节点/tmp_root_path数据成功!
...
...
...
...
开始删除第一个子目录节点/tmp_root_path/childPath1...
已经触发了NodeChildrenChanged事件!
第一个子目录节点/tmp_root_path/childPath1删除成功!
...
...
...
...
开始删除第二个子目录节点/tmp_root_path/childPath2...
第二个子目录节点/tmp_root_path/childPath2删除成功!
...
...
...
...
开始删除根目录节点/tmp_root_path...
根目录节点/tmp_root_path删除成功!
...
...
...
...
释放ZooKeeper连接成功!
        也是只监控一次,但是,但是,但是,它只监控根目录子节点的增减情况,至于数据是否发生变化,完全不会监控!这也正是为什么修改第一个子节点数据没有触发,而删除第一个子节点时会触发NodeChildrenChanged事件的原因!


        结论:

         getChildren()方法仅仅监控对应节点直接子目录的一次变化,但是只会监控直接子节点的增减情况,不会监控数据变化情况!若要每次对应节点发生增减变化都被监测到,那么每次都得先调用getChildren()方法获取一遍节点的子节点列表!










相关实践学习
基于MSE实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
18天前
|
SQL Java API
使用Java Stream API简化集合操作
使用Java Stream API简化集合操作
|
8天前
|
人工智能 API
阿里云微服务引擎及 API 网关 2024 年 6 月产品动态
阿里云微服务引擎及 API 网关 2024 年 6 月产品动态
113 14
|
15天前
|
运维 Cloud Native 应用服务中间件
阿里云微服务引擎 MSE 及 API 网关 2024 年 06 月产品动态
阿里云微服务引擎 MSE 面向业界主流开源微服务项目, 提供注册配置中心和分布式协调(原生支持 Nacos/ZooKeeper/Eureka )、云原生网关(原生支持Higress/Nginx/Envoy,遵循Ingress标准)、微服务治理(原生支持 Spring Cloud/Dubbo/Sentinel,遵循 OpenSergo 服务治理规范)能力。API 网关 (API Gateway),提供 APl 托管服务,覆盖设计、开发、测试、发布、售卖、运维监测、安全管控、下线等 API 生命周期阶段。帮助您快速构建以 API 为核心的系统架构.满足新技术引入、系统集成、业务中台等诸多场景需要
|
18天前
|
负载均衡 安全 Java
Java中的服务治理与API网关实现
Java中的服务治理与API网关实现
|
17天前
|
监控 安全 Java
在Java中集成第三方API调用的最佳实践
在Java中集成第三方API调用的最佳实践
|
19天前
|
Java API
如何利用Java Stream API简化集合操作?
如何利用Java Stream API简化集合操作?
|
22天前
|
开发框架 Java API
Java中的REST API开发详解
Java中的REST API开发详解
|
16天前
|
并行计算 Java 数据挖掘
Java面试题:解释Java中的Stream API及其操作
Java面试题:解释Java中的Stream API及其操作
22 0
|
18天前
|
Java API
如何利用Java Stream API简化集合操作?
如何利用Java Stream API简化集合操作?