探究Zookeeper--watcher真的是一次性吗?

目录

前言

watcher注册流程

客户端发起注册

服务端注册处理

事件通知流程

事件冒泡

ZK如何实现冒泡

再看WatcherMode

触发后处理

问题小结

设置Watcher的WatchMode

NodeChildrenChanged怎么收到

小结

未尽事宜


前言

最近在读ZK的watch相关源码,看到其中的watch有持久选项,以为自己发现不同的东西。从客户端注册到服务端源码捋了一遍发现,最终还是一次性使用的。

watcher注册流程

客户端发起注册

发起流程如下图所示,客户端通过SendThread发送注册请求到ZkServer端;ZkServer端通过WatcherManager管理所有的Watcher,ZkClient通过ZKWatcherManager管理所有的watcher。图片来自Zookeeper的Watcher 机制的实现原理

探究Zookeeper--watcher真的是一次性吗?_第1张图片

服务端注册处理

服务端ServerCnxn收到请求后,处理流程如下图所示,可见最终DataTree调用WatcherManager中的add/removeWatch方法完成注册。

探究Zookeeper--watcher真的是一次性吗?_第2张图片

但阅读源码会发现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决定是否通知。

探究Zookeeper--watcher真的是一次性吗?_第3张图片

事件冒泡

开发过用户界面端程序的小伙伴都知道事件冒泡这个概念,如下图为网页HTML中的事件捕获与冒泡过程。事件捕获过程是从父元素的组件,逐层下推,最终的最小颗粒度的组件上。事件冒泡过程,则是从最小颗粒度逐层上冒,直到顶层组件。此处,重点关注事件冒泡过程。

探究Zookeeper--watcher真的是一次性吗?_第4张图片

ZK如何实现冒泡

其中有个PathIterator类,基于使用“/”逐层遍历,如果事件产生路径为/debug/task/v1,冒泡过程如下,依次遍历:

/debug/task/v1

/debug/task

/debug

/

直到迭代至根节点/结束。有个事件冒泡,是不是相关path的watcher都会被通知到呢?显然不是。

再看WatcherMode

通过冒泡我们已经知道,通过path迭代获取到相关的watcher,但是否要通知还得看WatcherMode,具体规则如下:

1. 如果当前Path,Watcher是迭代订阅,且事件类型不是 NodeChildrenChanged,则加入待通知watch;否则进入2

2. 如果是当前节点,则直接加入待通知watch;显然,如果是在父节点,但不是迭代订阅,是无法收到通知的;

因此如果在当前路径上添加迭代订阅,是获取不到NodeChildrenChanged事件的。

触发后处理

如果当前Path,Watcher的WatcherMode是持久的,那么不做处理,否则从WatcherManager中删除,那么后续事件也就不会再被通知。

问题小结

(1)如果在客户端发起watch时指定下WatchMode为持久订阅,那么Watcher就可以重复使用?

(2)如果在当前节点的订阅是迭代的,那么收不到NodeChildrenChanged,但为什么实际使用中可以收到呢?

设置Watcher的WatchMode

首先看下watcher接口,除去相关枚举,定义如下图,其中并没有获取WatcherMode的说明方法,并且在客户端和服务端的实现中也没有对该watcher扩展的实现。所以,客户端无法设置WatchMode。因此对当前节点的非递归watcher只有1次有效期。

public interface Watcher {
     void process(WatchedEvent event);
}

NodeChildrenChanged怎么收到

DataTree.getChildren()订阅的是当前路径/data/tree/test,并且当有/data/tree/test/a1变更时,会单独触发/data/tree/test的NodeChildrenChanged和/data/tree/test/a1对应的NodeCreated。所以NodeChildrenChanged最终是可以正常收到的。

小结

  1. 客户端发起watcher不能设置watcheMode,只能以默认的STANDARD,发起watch;
  2. 服务端对不持久订阅,仅触发一次,然后从watcherManager中删除,后续的事件与该watcher没有关系;
  3. NodeChildrenChanged是由于两波独立的触发,因此仍然可以正常收到;
  4. 应用自己发起的watcher是一次性的,想实现类似持久订阅需要在事件触发后重新发起;

未尽事宜

实际的应用中,在watcher失效后客户端仍然后重新发起watch,与实现持久watch相比差别有多大。

你可能感兴趣的:(笔记,zookeeper,java)