目录
前言
watcher注册流程
客户端发起注册
服务端注册处理
事件通知流程
事件冒泡
ZK如何实现冒泡
再看WatcherMode
触发后处理
问题小结
设置Watcher的WatchMode
NodeChildrenChanged怎么收到
小结
未尽事宜
最近在读ZK的watch相关源码,看到其中的watch有持久选项,以为自己发现不同的东西。从客户端注册到服务端源码捋了一遍发现,最终还是一次性使用的。
发起流程如下图所示,客户端通过SendThread发送注册请求到ZkServer端;ZkServer端通过WatcherManager管理所有的Watcher,ZkClient通过ZKWatcherManager管理所有的watcher。图片来自Zookeeper的Watcher 机制的实现原理
服务端ServerCnxn收到请求后,处理流程如下图所示,可见最终DataTree调用WatcherManager中的add/removeWatch方法完成注册。
但阅读源码会发现WatchMode和WatchModeManager两类。其中WatchMode的定义如下,可见默认的watcherMode为非递归,非持久,正是这个PERSIST引起了我的注意。
public enum WatcherMode {
STANDARD(false, false),
PERSISTENT(true, false),
PERSISTENT_RECURSIVE(true, true);
public static final WatcherMode DEFAULT_WATCHER_MODE = WatcherMode.STANDARD;
private final boolean isPersistent;
private final boolean isRecursive;
WatcherMode(boolean isPersistent, boolean isRecursive) {
this.isPersistent = isPersistent;
this.isRecursive = isRecursive;
}
public boolean isPersistent() {
return isPersistent;
}
public boolean isRecursive() {
return isRecursive;
}
}
WatcherMode的后面再聊,先看WatcherModeManager。通过阅读代码明白,其作用是用于标识是否有递归订阅存在,以及获取递归订阅的watcher集合,避免遍历整个watcher集合来查找递归订阅,提高订阅模式的判定效率。
显然,相关的watch事件都是由服务端发起,通知到客户端。因此客户端的逻辑相对简单,仅做事件接收处理,此处重点关注服务端。服务端通过事件冒泡的方式,获取对应的watcher,然后基于WatcherMode决定是否通知。
开发过用户界面端程序的小伙伴都知道事件冒泡这个概念,如下图为网页HTML中的事件捕获与冒泡过程。事件捕获过程是从父元素的组件,逐层下推,最终的最小颗粒度的组件上。事件冒泡过程,则是从最小颗粒度逐层上冒,直到顶层组件。此处,重点关注事件冒泡过程。
其中有个PathIterator类,基于使用“/”逐层遍历,如果事件产生路径为/debug/task/v1,冒泡过程如下,依次遍历:
/debug/task/v1
/debug/task
/debug
/
直到迭代至根节点/结束。有个事件冒泡,是不是相关path的watcher都会被通知到呢?显然不是。
通过冒泡我们已经知道,通过path迭代获取到相关的watcher,但是否要通知还得看WatcherMode,具体规则如下:
1. 如果当前Path,Watcher是迭代订阅,且事件类型不是 NodeChildrenChanged,则加入待通知watch;否则进入2
2. 如果是当前节点,则直接加入待通知watch;显然,如果是在父节点,但不是迭代订阅,是无法收到通知的;
因此如果在当前路径上添加迭代订阅,是获取不到NodeChildrenChanged事件的。
如果当前Path,Watcher的WatcherMode是持久的,那么不做处理,否则从WatcherManager中删除,那么后续事件也就不会再被通知。
(1)如果在客户端发起watch时指定下WatchMode为持久订阅,那么Watcher就可以重复使用?
(2)如果在当前节点的订阅是迭代的,那么收不到NodeChildrenChanged,但为什么实际使用中可以收到呢?
首先看下watcher接口,除去相关枚举,定义如下图,其中并没有获取WatcherMode的说明方法,并且在客户端和服务端的实现中也没有对该watcher扩展的实现。所以,客户端无法设置WatchMode。因此对当前节点的非递归watcher只有1次有效期。
public interface Watcher {
void process(WatchedEvent event);
}
DataTree.getChildren()订阅的是当前路径/data/tree/test,并且当有/data/tree/test/a1变更时,会单独触发/data/tree/test的NodeChildrenChanged和/data/tree/test/a1对应的NodeCreated。所以NodeChildrenChanged最终是可以正常收到的。
实际的应用中,在watcher失效后客户端仍然后重新发起watch,与实现持久watch相比差别有多大。