Watcher详解、接口
在 ZooKeeper 中, 接口类 Watcher 用于表示一个标注你的事件处理器,其定义了事件通知相关的逻辑,包含 KeeperState 和 EventType 两个枚举类,分别代表了通知状态和事件类型,同时定义了事件的回调方法:process(WatchedEvent event)
如果Watcher
Watcher 触发条件:
增、删、改 ( 重复修改也会触发,因为他只告诉你变更了,不告诉你变更多少,需要 C 自己去拿)
abstract public void process ( WatchedEvent event )。
process() 是 Watch 接口中的回调方法。当 ZooKeeper 向客户端发送一个 Watcher 时间通知时,客户端就会对相应的 process 方法进行回调,从而实现对事件的处理。 like this:syncNodes()方法。
- private synchronized void initNodes(List<String> nodes) {
- // 根据zk节点,判断是否需要处理
- }
- private void syncNodes() {
- try {
- List<String> nodes = zookeeper.getChildren(ArbitrateConstants.NODE_NID_ROOT, new AsyncWatcher() {
- public void asyncProcess(WatchedEvent event) {
- syncNodes();// 回调方法继续关注node节点变化
- }
- });
- initNodes(nodes);
- } catch (KeeperException e) {
- syncNodes();
- } catch (InterruptedException e) {
- // ignore
- }
- }
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 机制设计,在网络开销和服务端内存开销上都是非常廉价的。
最新文章
- char、varchar、text和nchar、nvarchar、ntext的区别
- C#以管理员身份运行程序
- Android调用系统 Set As Intent
- WPF RichTextBox读取存储文本的方法和常用属性
- mac 版本的 Google 网盘 走代理
- vs2012C#编程环境设置智能提示
- 使用fastdfs-zyc监控FastDFS文件系统
- 从MongoDB的ObjectId中获取时间信息
- Linux命令学习总结:ls
- 【原创】讲讲亿级PV的负载均衡架构
- XSS(四)攻击防御
- Markdown字体大小与颜色
- 如果你的shiro没学明白,那么应该看看这篇文章,将shiro整合进springboot
- Boltzmann机神经网络python实现
- 【刷题】BZOJ 4443 [Scoi2015]小凸玩矩阵
- IIS应用程序池自动停止,报503错误解决方法
- 2、ambari搭建HDP集群
- IE9以下不支持placeholder属性
- 基于微服务的父maven依赖
- Python基础学习之语句和语法
热门文章
- HDU 1754 I Hate It【线段树 单点更新】
- swift语言点评十一-Methods
- swift语言点评二
- 字符串格式时间转Date格式
- bzoj2333 [SCOI2011]棘手的操作(洛谷3273)
- LightOJ-1336 Sigma Function 唯一分解定理 巧妙使用sqrt()等算数目
- 【图灵杯 J】简单的变位词
- 【转】Hook钩子C#实例
- java里的一些特别值得注意的地方
- how to deal with &;quot;no such file error or diretory&;quot; error for a new programmer in QT creator