zookeeper本质上是一个文件系统,目录、文件节点的创建有一定规则,这些规则应用分布式系统中,协调各节点之间的服务,常用的用法有服务发现、服务注册、服务通知等。
本文讲解监听api的用法,如何收到操作节点的通知,使用的客户端是java版本。
熟悉zookeeper客户端api的应该知道,客户端有以下几中方法,不用记住,一看便知。
-
- create,创建节点,根据持久化规则,分为永久节点和临时节点。
-
- setData,更新节点,修改节点存储的内容。
-
- getData,读取节点,读取节点存储的内容。
-
- delete,删除节点。
-
- 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();
}
}
}