ZooKeeper Watch Java API浅析getChildren

本文涉及的产品
服务治理 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实现微服务的全链路灰度
通过本场景的实验操作,您将了解并实现在线业务的微服务全链路灰度能力。
相关文章
|
15天前
|
设计模式 Java API
Java 可扩展 API 设计:打造灵活的应用架构
【4月更文挑战第27天】设计可扩展的 API 是构建灵活、易于维护的应用程序架构的关键。Java 提供了丰富的工具和技术来实现这一目标,使开发者能够构建具有高度可扩展性的应用程序。
37 4
|
1天前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的深度解析
【5月更文挑战第12天】本文将深入探讨Java 8中的两个重要新特性:Lambda表达式和Stream API。我们将从基本概念入手,逐步深入到实际应用场景,帮助读者更好地理解和掌握这两个新特性,提高Java编程效率。
11 2
|
2天前
|
Java API
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
【JAVA进阶篇教学】第三篇:JDK8中Stream API使用
|
4天前
|
网络协议 Dubbo Java
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
【网络编程】理解客户端和服务器并使用Java提供的api实现回显服务器
9 0
|
4天前
|
SQL Java 数据库连接
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
9 0
|
11天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【5月更文挑战第1天】本文将介绍Java 8中的两个重要特性:Lambda表达式和Stream API。Lambda表达式是一种新的函数式编程语法,可以简化代码并提高可读性。Stream API是一种用于处理集合的新工具,可以方便地进行数据操作和转换。通过结合Lambda表达式和Stream API,我们可以更加简洁高效地编写Java代码。
|
15天前
|
Java API 开发者
【专栏】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程
【4月更文挑战第27天】Java 8的Stream API是处理集合数据的新方式,强调简洁和声明式编程。它基于延迟执行和惰性求值,提供创建、中间操作(如filter、map)和终端操作(如forEach、collect)。示例展示了如何通过Stream排序、过滤、映射和聚合数据。
|
1月前
|
监控 负载均衡 Cloud Native
ZooKeeper分布式协调服务详解:面试经验与必备知识点解析
【4月更文挑战第9天】本文深入剖析ZooKeeper分布式协调服务原理,涵盖核心概念如Server、Client、ZNode、ACL、Watcher,以及ZAB协议在一致性、会话管理、Leader选举中的作用。讨论ZooKeeper数据模型、操作、会话管理、集群部署与管理、性能调优和监控。同时,文章探讨了ZooKeeper在分布式锁、队列、服务注册与发现等场景的应用,并在面试方面分析了与其它服务的区别、实战挑战及解决方案。附带Java客户端实现分布式锁的代码示例,助力提升面试表现。
124 2
|
4月前
|
消息中间件 Java 网络安全
JAVAEE分布式技术之Zookeeper的第一次课
JAVAEE分布式技术之Zookeeper的第一次课
72 0
|
2月前
|
监控 NoSQL Java
Zookeeper分布式锁
Zookeeper分布式锁
90 1