Watcher详解 工作机制, Watcher客户端注册、Watcher 服务端注册

Watcher详解、接口

    在 ZooKeeper 中, 接口类 Watcher 用于表示一个标注你的事件处理器,其定义了事件通知相关的逻辑,包含 KeeperState 和 EventType 两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)

如果Watcher

Watcher 触发条件:

    增、删、改 ( 重复修改也会触发,因为他只告诉你变更了,不告诉你变更多少,需要 C 自己去拿)


abstract public  void process ( WatchedEvent event )。

    process() 是 Watch 接口中的回调方法。当 ZooKeeper 向客户端发送一个 Watcher 时间通知时,客户端就会对相应的 process 方法进行回调,从而实现对事件的处理。 like thissyncNodes()方法。

  1. private synchronized void initNodes(List<String> nodes) {  
  2.     // 根据zk节点,判断是否需要处理  
  3. }  
  4.   
  5. private void syncNodes() {  
  6.         try {  
  7.             List<String> nodes = zookeeper.getChildren(ArbitrateConstants.NODE_NID_ROOT, new AsyncWatcher() {  
  8.   
  9.                 public void asyncProcess(WatchedEvent event) {  
  10.                     syncNodes();// 回调方法继续关注node节点变化  
  11.                 }  
  12.             });  
  13.   
  14.             initNodes(nodes);  
  15.         } catch (KeeperException e) {  
  16.             syncNodes();  
  17.         } catch (InterruptedException e) {  
  18.             // ignore  
  19.         }  
  20.     }  

 

    Watcher 设置是开发中最常见的,需要搞清楚watcher的一些基本特征,对于exists、getdata、getchild对于节点的不同操作会收到不同的 watcher信息


对父节点的变更以及子节点的变更都不会触发watcher,而对watcher本身节点以及子节点的变更会触发watcher

继续。

ZooKeeper 使用 WatchedEvent 对象封装服务端事件并传递给 Watcher, 从而方便回调方法 process 对服务端事件进行处理。

WatcherEvent 实体实现了序列化接口,因此可以用于网络传输。数据结构如下。

Class WatcherEvent{
  type:int
  state:int
  path:String
}

state=-112 会话超时状态
state= -113 认证失败状态
state=  1 连接建立中
state= 2 (暂时不清楚如何理解这个状态,ZOO_ASSOCIATING_STATE)
state=3 连接已建立状态
state= 999 无连接状态

type=1 创建节点事件
type=2 删除节点事件
type=3 更改节点事件
type=4 子节点列表变化事件
type= -1 会话session事件
type=-2 监控被移除事件


Watcher 发送过程。

        当服务端产生 WatchedEvent 事件之后,会调用 getWrapper 方法将自己包装成一个可序列化的 WatcherEvent 事件,以便于通过网络传输到客户端。客户端在接收到服务端的这个事件对象后,首先会将 WatcherEvent 事件还原成一个 WatchedEvent 事件。并传递给 process方法处理。 回调方法根据传入参数解析完整服务端事件。

Watcher 发送的数据
            无论是 WatcherEvent 还是 WatchedEvent,他对 ZooKeeper 服务端事件的封装都是极其简单的。 当 /Test/test1/1_1节点发生变更时,服务端会发送给客户端一个“ZNode数据变更“ 事件,客户端也只能接收到如下信息:
    KeeperState : SyncConnected
    EventType : NodeDataChanged
    Path : /zk-b
            也就是说,客户端无法直接从该事件中获取到对应数据节点的原始数据内容,以及变更后的新数据内容。而是客户端再次主动去重新获取数据。——这个也是 ZooKeeper 一个非常重要的特性。

Watcher 工作机制

服务端发送不处理逻辑、客户端发送并处理逻辑。

客户端注册 Watcher
        创建一个 new ZooKeeper() 客户端对象实例时,可以传入一个 Watcher .
      new ZooKeeper(String connectString,int sessionTimeout, Watcher watcher)  
        这个Watcher 将作为整个 ZooKeeper 回话期间的默认 Watcher,会一直被保存在客户端 ZKWatchManager 的 defaultWatcher 中。 另外,ZooKeeper 客户端也可以通过 getData、 getChildren 和 exist 三个接口来向 ZooKeeper 服务器注册 Watcher。 列举个getData 例:
            public byte[] getData(String path,boolean watch, Stat stat)
      public byte[] getData(final String path,Watcher watch, Stat stat)
        第一个通过一个 boolean 参数来标识是否使用默认 Watcher 进行注册,具体注册逻辑与第二个接口一致。
注册 Watcher 后
        在 getData 接口注册 Watcher 后,客户端首先会对当前客户端请求 request 进行标记, 将其设置为 ”使用Watcher“监听。同时会封装一个 Watcher 的注册信息,WatchRegistration 对象。 用于暂时保存数据节点的路径 和 Watcher 的对应关系。

Packet 与 WatchRegistration。 
Packet 类
        在 ZooKeeper 中 Packet 可以看做是一个最小的通信协议单元用于进行客户端与服务端之间的网络传输任何需要传输的对象都需要包装成一个 Packet 对象。 因此,在 ClientCnxn 中 WatchRegistration 又会被封装到 Packet 中去, 然后放入发送队列中等待客户端发送。随后,ZooKeeper 客户端就会向服务端发送这个请求,同时等待请求的返回。完成请求发送后,会由客户端 SendThread 线程的 readResponse 方法负责接收来自服务端的相应, finishPacket 方法会从 Packet 中取出对象的 Watcher 并注册到 ZKWatchManager 中去。

        WatchRegistration 封装到了 Packet 对象中去,但事实上,在底层的网络传输过程中,没有将 WatchRegistration 对象完全的序列化到底层字节数组中去。ZooKeeper 只会将 requestHeader 和 request 两个属性进行序列化。 也就是说,即使WatchRegistration 对象呗封装在了 Packet 中,但是并没有被序列化到底层字节数组中去。因此也就不会进行网络传输了。

客户端 Watcher 的注册流程如下:

       

服务端注册 Watcher 
服务端处理 Watcher 的序列图:


1 FinalRequest Processor.processRequest( ) 中会判断当前请求是否是需要注册 Watcher:
        1) 如果 ZooKeeper 判断当前客户端需要进行 Watcher 注册,于是就会将当前的 ServerCnxn 对象和数据路径传入 getData 方法中去。 ServerCnxn 是一个 ZooKeeper 客户端和服务器之间的连接接口,代表了一个客户端和服务器的连接。我们可以 ServerCnxn 看做是一个 Watcher 对象。因为他实现了 Watcher 的 process 接口

WatcherManager
    是 ZooKeeper 服务端 Watcher 的管理者,其内部管理的 watchTable 和 watch2Paths 两个存储结构,分别从两个维度对 Watcher 进行存储。
    1) watchTable     是从数据节点路径的粒度管理 Watcher。
    2) watch2Paths   是从 Watcher 的粒度来控制事件触发的数据节点
在服务端,DataTree 中会托管两个 WatchManager, 分别是 dataWatches (数据变更Watch) 和 childWatches(子节点变更Watch)。


Watcher 触发逻辑
     1    封装 WatchedEvent。
            将通知状态 - KeeperState、事件类型 - EventType、节点路径 - Path 封装成一个 WatchedEvent 对象
     2    查询 Watcher。 
            根据路径从 watchTable 中取出对应的 Watcher。若无-没注册 直接退出。若有-注册过 将数据提取出来 同时 从wTable w2Paths 中删除掉。 Watcher 在服务端也是一次性的
     3    调用 process 方法触发 Watcher
             ZooKeeper 会把当前请求对应的 ServerCnxn 作为一个 Watcher 存储,因此调用 process 方法,事实上就是 ServerCnxn 对应的 process 方法。

客户端回调 Watcher
      1    反序列化
                字节流转换成 WatcherEvent 对象
      2    处理 chrootPath
                如果客户端设置了 chrootPath 属性,那么需要对服务器传过来的完整节点路径进行 chrootPath 处理,生成客户端的一个相对节点路径。 例如客户端 cPath路径 /Test/test1 那么针对服务端传过来的相应包含的节点路径为/Test/test1/1_19, 经过chrootPath 处理后 会变成一个相对路径:/ 1_19.
      3    还原 WatchedEvent
                WatcherEvent 转换成 WatchedEvent.
      4    回调 Watcher。
                最后将 WatcherEvent 对象交给 EventThread 线程,在下一个轮询周期中进行 Watcher 回调。

EventThread 处理时间通知。
        SendThread 接收到服务端的通知事件后,会通过调用 EventThread.queueEvent 方法将事件传给 EventThread 线程。queueEvent 方法首先会根据该通知事件,从 ZKWatchManager 中取出所有相关的 Watcher 客户端识别出 事件类型 EventType 后,会从相应的 Watcher 存储 (即3个注册方法)中去除对应的 Watcher。获取到相关的所有 Watcher 后,会将其放入 waitingEvents 这个队列去。
    
        注意 此处调用的是 remove 接口。 客户端的 Watcher 同样也是一次性的。即一旦被触发,该Watcher 就失效了。
       ① 3个注册方法: dataWatches、existWatcher 或 childWatcher 中的一个或多个
       ② waitingEvents 是一个待处理 Watcher 的队列,EventThread 的 run 方法会不断对该队列进行处理


Watcher 特性总结:
    一次性 
           一旦一个 Watcher 被触发,ZooKeeper 都会将其从相应的存储中移除。 因此开发人员在 Watcher 的使用上需要重复注册。
    客户端串行执行
           客户端 Watcher 回调 是一个串行同步的过程。 这里是为了保证顺序,所以设计时千万不要为了一个 Watcher 影响了整个客户端的 Watcher 回调
    轻量
            WatchedEvent 是 ZooKeeper 整个 Watcher 通知机制的最小通知单元,这个数据结构只有3部分。 通知状态,事件类型,和节点路径。 也就是说 Watcher 只会告诉客户端 那个节点发生了什么事件。 而不会说明具体内容。 需要客户端主动重新去获取数据。 这个是 Watcher 机制的一个重要特性。

     另外,客户端向服务端注册 Watcher 的时候, 并不会吧客户端真实的 Watcher 对象 传递到服务端,而是充值在客户端请求中使用 boolean 类型属性进行了 标记,同时服务端也仅仅保存了当前连接的 ServerCnxn。
    如此轻量的 Watcher 机制设计,在网络开销和服务端内存开销上都是非常廉价的。








God has given me a gift. Only one. I am the most complete fighter in the world. My whole life, I have trained. I must prove I am worthy of someting. rocky_24
原文地址:https://www.cnblogs.com/rocky24/p/4859206.html