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()方法获取一遍节点的子节点列表!