3.6版本的zookeeper中的watch机制

Watch的分类

客户端可分为三类,服务端可分为两类

  1. dataWatches:表示监听的是某节点的数据变化,比如数据的新增、修改、删除。
  2. childWathes:表示监听的是某节点的孩子节点的变化,如果某个孩子节点新增或删除了,会触发其父节点上的NodeChildrenChanged事件。
  3. existWatches(只在客户端):服务端无需单独触发该事件,由客户端接收其他事件自己判断,比如客户端接收到一个NodeCreated事件,客户端如果注册了existWatches,那么existWatches就会被触发。

不同的Watcher事件类型会触发哪些Watch呢?

  • None
    • defaultWatcher
    • 所有dataWatches(触发完可能清空)
    • 所有existWatches(触发完可能清空)
    • 所有childWatches(触发完可能清空)
    • 所有的persistentWatches
    • 所有的persistentRecursiveWatches(持久递归watch,会导致所有的节点都会进行递归,但只有对应的path会触发watch,文章末尾会详细讲解)
  • NodeDataChanged
  • NodeCreated
    • 对应path的dataWatches(立马移除,先移除再执行)
    • 对应path的existWatches(立马移除,先移除再执行,如果绑定了existWatche,当节点被新增或修改时触发)
    • 对应path的persistentWatches
    • 对应path的每层父path对应的persistentRecursiveWatches(这里有点难理解,persistentRecursiveWatches中存储的就是Watcher,这里根据当前WatchedEvent对应的path,找到该path的各层父path,然后再遍历各层父path,根据父path去从persistentRecursiveWatches集合中找当前父path是不是有对应的Watcher,如果有就添加到result中去,等待执行,这就是递归,一个子节点的数据发生了变化,父节点上的Watcher也能被触发)
  • NodeChildrenChanged
    • 对应path的childWatches(立马移除,先移除再执行)
    • 对应path的persistentWatches
    • 对应path的每层父path对应的persistentRecursiveWatches
  • NodeDeleted
    • 对应path的dataWatches(立马移除,先移除再执行)
    • 对应path的existWatches(立马移除,先移除再执行,如果绑定了existWatche,当节点被删除时触发)
    • 对应path的childWatches(立马移除,先移除再执行)
    • 对应path的persistentWatches
    • 对应path的每层父path对应的persistentRecursiveWatches

不同的操作会触发的Watcher事件类型

  • 创建节点
    • 触发当前节点的NodeCreated事件
    • 触发当前节点的父节点的NodeChildrenChanged事件
  • 删除节点
    • 触发当前节点的NodeDeleted事件
    • 触发当前节点的父节点的NodeChildrenChanged事件
  • 修改节点
    • 触发当前节点的NodeDataChanged事件

  需要注意的是:NodeChildrenChanged事件不会触发persistentRecursiveWatches,下面列出所测试的数据。

下面所测的是persistentRecursiveWatches与persistentWatches

在这里插入图片描述
两个addWatch操作的间隔需要睡眠一下,不然会报错。

 PERSISTENTWatchedEvent 绑在/gwokgwok上,
       1、在其下create子节点是:NodeChildrenChanged path:/gwokgwok
       2、在其下delete子节点是:NodeChildrenChanged path:/gwokgwok
       2、改变/gwokgwok的值:NodeDataChanged path:/gwokgwok
       3、改变/gwokgwok下的子节点的值,没反应。
 PERSISTENT_RECURSIVEWatchedEvent 绑在/gwok上,
       1、在其下create子节点是:NodeCreated path:/gwok/b
       2、在其下delete子节点是:NodeDeleted path:/gwok/b
       3、改变其子节点的值:NodeDataChanged path:/gwok/b
       4、改变节点自己的值:NodeDataChanged path:/gwok

各个状态与Watcher事件类型之间的关系

状态一KeeperState.Disconnected(0):此时客户端处于断开连接状态,和ZK集群都没有建立连接。

  1. EventType.None(-1)事件
    触发条件:一般是在与服务器断开连接的时候,客户端会收到这个事件。

状态二KeeperState. SyncConnected(3):此时客户端处于连接同步状态

  1. EventType.None(-1)事件
    触发条件:客户端与服务器成功建立会话之后,会收到这个通知。
  2. EventType. NodeCreated (1)事件
    触发条件:所关注的节点被创建。
  3. EventType. NodeDeleted (2)事件
    触发条件:所关注的节点被删除。
  4. EventType. NodeDataChanged (3)事件
    触发条件:所关注的节点的内容有更新。注意,这个地方说的内容是指数据的版本号dataVersion。因此,即使使用相同的数据内容来更新,还是会收到这个事件通知的。无论如何,调用了更新接口,就一定会更新dataVersion的。
  5. EventType. NodeChildrenChanged (4)事件
    触发条件:所关注的节点的子节点有变化。这里说的变化是指子节点的个数和组成,具体到子节点内容的变化是不会通知的。

状态三: KeeperState. AuthFailed(4)

  1. EventType.None(-1)事件
    状态四:KeeperState. Expired(-112)
  2. EventType.None(-1)事件

那么persistentRecursiveWatches到底是如何使得全部节点都需要递归的呢?

  我们先看一下注册persistentRecursiveWatches的时候的经过:当客户端发来一个addWatch请求的时候,由于addwatch请求既不是写请求也不是事务请求,所以在PrepRequestProcessor里request是将日志头和内容清空,其后也没有再为其赋值,所以在SyncRequestProcessor里不会进行记录日志和持久化,到了最后的FinalRequestProcessor,执行zks.processTxn(request)(124行)的时候由于既不是写请求又不是事务请求所以会返回new ProcessTxnResult(),然后再到(413行)case OpCode.addWatch: ,进行addWatch方法(是执行实现类WatchManager,不是其接口IWatchManager的默认方法),在里面执行watcherModeManager的setWatcherMode方法,看如下图
图一
图二
  watcherModes是一个concurrenthashmap由于该key是第一次put的,不存在oldvalue,所以返回的null,所以图二的if(oldeMode==null)会执行,随后执行下一个if将 recursiveQty加1。

  而persistentWatches的注册和上面差不多,只是在adjustRecursiveQty方法里第二个if(oldMode.isRecursive() != newMode.isRecursive())不会成立,所以不会干扰到recursiveQty,所以只有当addWatch设置了persistentRecursiveWatches的时候,再次addWatch重新设置成其他非递归的Watch【STANDARD或者PERSISTENT】的时候才会执行 recursiveQty减1的操作(recursiveQty.decrementAndGet(); ),因为这时候的oldMode就会是PERSISTENT_RECURSIVE,而newMode就是非递归的Watch,所以 if (oldMode.isRecursive() != newMode.isRecursive())会成立,但 if (newMode.isRecursive()) 不会成立。
在这里插入图片描述

我们再来看一下触发watcher事件时候所执行的步骤

@Override
public WatcherOrBitSet triggerWatch(String path, EventType type, WatcherOrBitSet supress) {
    
    


    // 注意KeeperState
    WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);


    Set<Watcher> watchers = new HashSet<>();


    // 拿到当前path的各层父path进行递归
   重点一 PathParentIterator pathParentIterator = getPathParentIterator(path);
    synchronized (this) {
    
    
        // 遍历路径
      重点二  for (String localPath : pathParentIterator.asIterable()) {
    
    
            // 当前path对应的节点上有几个watcher(可能存在多个客户端,而且每个客户端对同一节点)
            Set<Watcher> thisWatchers = watchTable.get(localPath);


            if (thisWatchers == null || thisWatchers.isEmpty()) {
    
    
                continue;
            }
            Iterator<Watcher> iterator = thisWatchers.iterator();
            // 遍历当前path上注册的Watcher
            while (iterator.hasNext()) {
    
    
                Watcher watcher = iterator.next();
                // 当前路径上的某个Watcher是不是递归的
                WatcherMode watcherMode = watcherModeManager.getWatcherMode(watcher, localPath);
                // 如果Watcher是递归的
                if (watcherMode.isRecursive()) {
    
    
                    // 当前触发的事件不是NodeChildrenChanged就能触发递归Watcher
                    // 那么也就是说,一个节点的孩子节点被删除了,是不会触发递归Watcher的
                    if (type != EventType.NodeChildrenChanged) {
    
    
                        watchers.add(watcher);
                    }
                } else if (!pathParentIterator.atParentPath()) {
    
    
                    watchers.add(watcher);
                    if (!watcherMode.isPersistent()) {
    
    
                        // 执行完移除
                        iterator.remove();
                        Set<String> paths = watch2Paths.get(watcher);
                        if (paths != null) {
    
    
                            paths.remove(localPath);
                        }
                    }
                }
            }
            if (thisWatchers.isEmpty()) {
    
    
                watchTable.remove(localPath);
            }
        }
    }


    if (watchers.isEmpty()) {
    
    
        if (LOG.isTraceEnabled()) {
    
    
            ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK, "No watchers for " + path);
        }
        return null;
    }




    for (Watcher w : watchers) {
    
    
        if (supress != null && supress.contains(w)) {
    
    
            continue;
        }
        // 执行Watcher,向客户端发送事件
        w.process(e);
    }


    switch (type) {
    
    
        case NodeCreated:
            ServerMetrics.getMetrics().NODE_CREATED_WATCHER.add(watchers.size());
            break;


        case NodeDeleted:
            ServerMetrics.getMetrics().NODE_DELETED_WATCHER.add(watchers.size());
            break;


        case NodeDataChanged:
            ServerMetrics.getMetrics().NODE_CHANGED_WATCHER.add(watchers.size());
            break;


        case NodeChildrenChanged:
            ServerMetrics.getMetrics().NODE_CHILDREN_WATCHER.add(watchers.size());
            break;
        default:
            // Other types not logged.
            break;
    }


    return new WatcherOrBitSet(watchers);
}

  上面的代码重点有标注,先看一下getPathParentIterator(path)这个方法,上文已经介绍了persistentRecursiveWatches在服务端注册的过程,所以当我们客户端有写请求过来服务端触发该watch的时候,就会到下图①这一步,这时候的recursiveQty已经是1了,所以会执行PathParentIterator.forAll(path);然后我们点进去看一下,看图②,我们可以看到其返回的是一个以maxLevel为Interger最大值的PathParentIterator —>new PathParentIterator(path, Integer.MAX_VALUE);

  而如果不是递归的呢?那就是执行PathParentIterator.forPathOnly(path);看图③,返其返回一个以maxLevel为0的PathParentIterator —>new PathParentIterator(path, 0);
 图①

图①

在这里插入图片描述

图②

在这里插入图片描述

图③

  那么返回了PathParentIterator对象后有什么用呢?我们接着看下一个标重点的方法pathParentIterator.asIterable()如下图
在这里插入图片描述
  我们看它返回的是PathParentIterator本身自己,就是上面返回的new PathParentIterator(xxxx,xxx),那么再接着看该对象是用在了for迭代方法里的,所以我们可以看下图PathParentIterator类是如何实现Iterator接口的next方法和hasNext方法的你就彻底明白了。
在这里插入图片描述
在这里插入图片描述

总结

  1.   若客户端设置了PERSISTENT_RECURSIVE模式的Watch,那么所有节点都会进行递归操作,而该watch会被触发事件的只有当被绑定的节点的内容被更新或其子节点的创建、删除、内容更新。【但NodeChildrenChanged事件不会触发到该watch】;
  2.   若客户端设置了PERSISTENT模式的Watch,那么其功能和STANDARD模式基本是一样的,只是多了一项持久的功能而已。【该持久并不是写入文件的持久化的意思,而是意味着该watch被触发后不会被删除】。

猜你喜欢

转载自blog.csdn.net/gwokgwok137/article/details/113920678