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. private void syncNodes() {
  5. try {
  6. List<String> nodes = zookeeper.getChildren(ArbitrateConstants.NODE_NID_ROOT, new AsyncWatcher() {
  7. public void asyncProcess(WatchedEvent event) {
  8. syncNodes();// 回调方法继续关注node节点变化
  9. }
  10. });
  11. initNodes(nodes);
  12. } catch (KeeperException e) {
  13. syncNodes();
  14. } catch (InterruptedException e) {
  15. // ignore
  16. }
  17. }

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 机制设计,在网络开销和服务端内存开销上都是非常廉价的。




Watcher详解 工作机制, Watcher客户端注册、Watcher 服务端注册的更多相关文章

  1. 源码详解openfire保存消息记录_修改服务端方式

    实现openfire消息记录通常有两种方式,修改服务端和添加消息记录插件. 今天,简单的说明一下修改服务端方式实现消息记录保存功能. 实现思路 修改前: 默认的,openfire只提供保存离线记录至o ...

  2. Android 异步通信:图文详解Handler机制工作原理

    前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将图文详解 Handler机制 的工作原理,希望你们会喜欢 目录 1. 定义 一套 Android 消息传递机制 2. ...

  3. zookeeper使用详解(命令、客户端、源码)

    1. zookeeper使用详解(命令.客户端.源码) 1.1. 前言   zookeeper我们常用来做分布式协调中间件,很多时候我们都接触不到它的原理和用法,我对他的了解也仅限于知道它可以做分布式 ...

  4. 详解slab机制

    转 详解slab机制 2015年01月15日 16:34:47 cosmoslhf 阅读数:12657   http://blog.csdn.net/u010246947/article/detail ...

  5. Comet技术详解:基于HTTP长连接的Web端实时通信技术

    前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...

  6. Spring Cloud 服务端注册与客户端调用

    Spring Cloud 服务端注册与客户端调用 上一篇中,我们已经把Spring Cloud的服务注册中心Eureka搭建起来了,这一章,我们讲解如何将服务注册到Eureka,以及客户端如何调用服务 ...

  7. java版gRPC实战之六:客户端动态获取服务端地址

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  8. C#使用Thrift简介,C#客户端和Java服务端相互交互

    C#使用Thrift简介,C#客户端和Java服务端相互交互 本文主要介绍两部分内容: C#中使用Thrift简介 用Java创建一个服务端,用C#创建一个客户端通过thrift与其交互. 用纯C#实 ...

  9. SpringCloud微服务实战:一、Eureka注册中心服务端

    1.项目启动类application.java类名上增加@EnableEurekaServer注解,声明是注册中心 1 import org.springframework.boot.SpringAp ...

随机推荐

  1. 【转载】犀利的 oracle 注入技术

    介绍一个在web上通过oracle注入直接取得主机cmdshell的方法. 以下的演示都是在web上的sql plus执行的,在web注入时 把select SYS.DBMS_EXPORT_EXTEN ...

  2. hdu 1051 - 贪心,水题

    题目链接 一堆小木棍,每个有两个属性值(l,w),对小木棍分组,每一组内的小木棍存在这样一个序列满足s1<=s2<=s3.....<=sn,[s1<=s2当且仅当s1.l< ...

  3. javascript常用代码(不完整版)

    求大神指点 Javascript嵌入式 <script typt:javascript>代码</script> 注释 //或者/*内容*/ 变量名赋值 Var 变量名 = 值 ...

  4. Thread-local storage

    Thread-local storage (TLS) is a computer programming method that uses static or global memory local ...

  5. redis数据库服务器开启的三种方式

    redis的启动方式1.直接启动  进入redis根目录,执行命令:  #加上‘&’号使redis以后台程序方式运行 1 ./redis-server & 2.通过指定配置文件启动  ...

  6. js Date() 时间函数处理 关于 toLocaleDateString()

    toLocaleDateString()方法的真正含义为「根据本地时间把Date对象的日期部分转换为字符串」,这意味着:在不同的浏览器或者服务器中,我们可能得到不同的字符串. 例如,将 Chrome ...

  7. 用centos镜像 制作本地yum源

    1.上传iso镜像 2.挂载镜像到相应目录 mkdir /yumiso #创建目录mount -t iso9660 /dev/cdrom/sr0 /yumiso #挂载镜像文件到对应目录 3.备份旧的 ...

  8. 升级glibc的感慨,

    1. 直接升级 glibc是gnu发布的libc库,即c运行库.glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc.glibc除了封装linux操作系统所提供的系统服务 ...

  9. Python学习笔记(二):字符串类型

    在上一篇随笔(https://www.cnblogs.com/g-qiang/p/10448813.html)中,说到 Python 有六种标准数据类型,而数字类型和字符串类型又是其中基本的数据类型. ...

  10. Camera Calibration 相机标定:Opencv应用方法

    本系列文章由 @YhL_Leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/49427383 Opencv中Camer ...