ZooKeeper Watcher 机制
前言
在 ZooKeeper 中,客户端可以向服务端注册一个监听器,监听某个节点或者其子节点列表,当监听对象发生变化时,服务端就会向指定的客户端发送通知,这是 ZooKeeper 中的 Watcher 机制,Watcher 机制是 ZooKeeper 中一个重要的特性,这篇文章就带大家了解下,底下是 Watcher 机制的执行过程:

从上图可以看到,Watcher 机制包括三个角色:客户端线程、客户端的 WatchManager 以及 ZooKeeper 服务器。Watcher 机制就是这三个角色之间的交互,整个过程分为注册、存储和通知三个步骤:
- 客户端向 ZooKeeper 服务器注册一个 Watcher 监听,
- 把这个监听信息存储到客户端的 WatchManager 中
- 当 ZooKeeper 中的节点发生变化时,会通知客户端,客户端会调用相应 Watcher 对象中的回调方法。
了解了整体的流程之后,接下来就来看下一些细节问题。
客户端处理
要了解 Watcher 机制,首先我们得知道什么时候客户端可以注册一个 Watcher 呢?通过查看 API 我们可以了解到,在创建 ZooKeeper 对象,或者是在读取数据时(即调用 getData、exists、getChildren 方法)可以注册一个 Watcher 监听,他们内部的实现都是一样的,这里我们就以 getData 方法为例来探究下 Watcher 机制的实现。
/**
* ZooKeeper.java
*/
public byte[] getData(String path, Watcher watcher, Stat stat) throws KeeperException, InterruptedException {
ZooKeeper.WatchRegistration wcb = null;
if (watcher != null) {
wcb = new ZooKeeper.DataWatchRegistration(watcher, path);
}
// ...
GetDataRequest request = new GetDataRequest();
request.setPath(serverPath);
request.setWatch(watcher != null);
ReplyHeader r = this.cnxn.submitRequest(h, request, response, wcb);
// ...
}
/**
* ClientCnxn.java
*/
public ReplyHeader submitRequest(RequestHeader h, Record request,
Record response, WatchRegistration watchRegistration,
WatchDeregistration watchDeregistration)
throws InterruptedException {
ReplyHeader r = new ReplyHeader();
Packet packet = queuePacket(h, r, request, response, null, null, null,
null, watchRegistration, watchDeregistration);
// ...
}
通过上面的代码我们可以了解到,Watcher 对象和其监听的路径会被封装在 WatchRegistration 对象中,然后在 ClientCnxn 还会被封装在 Packet 对象中。这个 Packet 可以被看做是一个最小的通信协议单元,用于进行客户端与服务端之间的网络传输。封装完成之后,将该请求发送给服务端,发送成功后,将 Watcher 相关信息存储到客户端的 ZKWatchManager 对象中,至此客户端的做的工作也就完成了。
不过,这里有一个细节问题,是不是每调用一次 getData 客户端都会把整个 Watcher 对象发送给客户端呢?如果是这样的话,多次 getData 的调用就会导致服务端内存的紧张,我们来看下 ZooKeeper 是怎么处理这个问题的。
/**
* Packet.java
*/
public void createBB() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
boa.writeInt(-1, "len"); // We'll fill this in later
if (requestHeader != null) {
requestHeader.serialize(boa, "header");
}
if (request instanceof ConnectRequest) {
request.serialize(boa, "connect");
// append "am-I-allowed-to-be-readonly" flag
boa.writeBool(readOnly, "readOnly");
}
}
}
通过查看 Packet 内部的 createBB 方法我们可以看到,Packet 在进行网络传输时仅仅是把 requestHeader 和 request 两个属性进行序列化,虽然 Watcher 被封装在 Packet 中,但是其并不会通过网络传输给服务端。request 对象里面的内容可以看上面 ZooKeeper 类中的 Request 对象,它主要给该 Packet 添加一个标识,让服务端判断该请求是否包含 Watcher 监听。
服务端处理
在源码中,服务端是由 ZooKeeperServer 实现的,在其内部,是由 RequestProcessor 接口来处理客户端的请求,我们来看下其中的一个实现类 FinalRequestProcessor。
/**
* FinalRequestProcessor
*/
public void processRequest(Request request) {
// ...
ServerCnxn cnxn = request.cnxn;
case OpCode.getData: {
// ...
byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
getDataRequest.getWatch() ? cnxn : null);
break;
}
}
当 FinalRequestProcesor 判断到该请求是一个 Watcher 监听时,会把 ServerCnxn 对象和监听路径传到 getData 方法里面去。这个 ServerCnxn 是什么呢?它是一个 ZooKeeper 客户端和服务端之间的连接接口,代表了一个客户端和服务器的连接,并且实现了 Watcher 的 process 方法,它最终会交由给 WatchManager 管理。
除了管理 Watcher,WatcherManager 还负责 Watcher 事件的触发,并移除那些已经被触发的 Watcher。由于其管理的 ServerCnxn 已经实现了 process 方法,因此当监听对象发生变化时,它就会调用 ServerCnxn 的 process 方法。
/**
* NIOServerCnxn
*/
public void process(WatchedEvent event) {
ReplyHeader h = new ReplyHeader(-1, -1L, 0);
WatcherEvent e = event.getWrapper();
sendResponse(h, e, "notification");
}
我们可以看到,服务端执行的逻辑很简单,只是在请求头中标记 -1,表明当前是一个通知,然后将该请求发送给客户端,具体的回调逻辑都是在客户端执行的。
客户端回调 Watcher
客户端在和 ZooKeeper 建立连接时,会启动 sendThread 和 eventThread 线程。
sendThread 线程负责发送请求给服务端,同时也接收服务端发送过来的响应,当它判断到响应中的 XID 标识为 -1,便将它作为一个通知类型的响应,将响应中的信息进行序列化,交给 eventThread 线程处理。
eventThread 会根据响应内容判断该通知对应的 Watcher 类型,从 ZKWatchManager 中取出所有相关的 Watcher,然后放到 waitingEvents 队列中,该队列时一个待处理 Watcher 的队列,eventThrad 每次从中取出一个 Watcher,然后进行串行同步处理,就是依次调用队列中 Watcher 的 process 的方法。
Watcher 特性总结
上面就是 Watcher 机制的整个执行流程了,最后就简单说下我认为 Watcher 机制中两个比较显著的特点。
第一个就是一次性,在整个流程中,不管是服务端还是客户端在处理 Watcher,当 Watcher 触发之后,就会将他们从本地内存中去除掉,如果还需要监听的话就需要反复注册。如果注册一个 Watcher 一直有效的话,那么当更新频繁时,对网络带宽和服务器的压力是很大的。
第二个就是轻量,客户端发送给服务器的请求中只是表明该请求是对哪个路径的监听,并没有把全部信息传给服务端,服务端给客户端做响应也是如此,它只是告诉它监听的节点或子列表发生变化了,具体的变化信息需要重新去服务端获取,这个轻量的设计使得网络带宽和服务器的压力大大减小了。
ZooKeeper Watcher 机制的更多相关文章
- Zookeeper watcher机制
一.watcher机制 1.针对每个节点的操作,都会有一个监督者-> watcher 2.当监控的某个对象(znode)发生了变化,则触发watcher事件 3.zk中的watcher是一次性的 ...
- Zookeeper Watcher 机制 -- 数据变更通知 ?
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...
- Zookeeper Watcher 机制 -- 数据变更通知 ?
Zookeeper 允许客户端向服务端的某个 Znode 注册一个 Watcher 监听,当服务 端的一些指定事件触发了这个 Watcher,服务端会向指定客户端发送一个事件通 知来实现分布式的通知功 ...
- zk的watcher机制的实现
转载:https://www.ibm.com/developerworks/cn/opensource/os-cn-apache-zookeeper-watcher/ http://blog.csdn ...
- 【Zookeeper】源码分析之Watcher机制(一)
一.前言 前面已经分析了Zookeeper持久话相关的类,下面接着分析Zookeeper中的Watcher机制所涉及到的类. 二.总体框图 对于Watcher机制而言,主要涉及的类主要如下. 说明: ...
- 【Zookeeper】源码分析之Watcher机制(二)
一.前言 前面已经分析了Watcher机制中的第一部分,即在org.apache.zookeeper下的相关类,接着来分析org.apache.zookeeper.server下的WatchManag ...
- 【Zookeeper】源码分析之Watcher机制(三)之Zookeeper
一.前言 前面已经分析了Watcher机制中的大多数类,本篇对于ZKWatchManager的外部类Zookeeper进行分析. 二.Zookeeper源码分析 2.1 类的内部类 Zookeeper ...
- Zookeeper的Watcher 机制的实现原理
基于 Java API 初探 zookeeper 的使用: 先来简单看一下API的使用: public class ConnectionDemo { public static void main(S ...
- 【Zookeeper】源码分析之Watcher机制(二)之WatchManager
一.前言 前面已经分析了Watcher机制中的第一部分,即在org.apache.zookeeper下的相关类,接着来分析org.apache.zookeeper.server下的WatchManag ...
随机推荐
- JavaScript 对象的创建和操作
<script> // 对象是属性的无序集合,每个属性都是一个名/值对. 属性名称是一个字符串. // 对象种类 // 内置对象(nativ ...
- 关于在JSP页面识别不了EL表达式的情况
今天在JSP页面接收Controller返回的数据user_nickname,使用EL表达式显示数据发现在页面输出的始终是字符串${user_nickname} 经过查阅资料,问题在于使用的web.x ...
- Python Ethical Hacking - Intercepting and Modifying Packets
INTERCEPTING & MODIFYING PACKETS Scapy can be used to: Create packets. Analyze packets. Send/rec ...
- stm32-HAL库串口收发
串口发送 重写fputc函数 /* 优点 直接使用printf函数,发送数据长度无限制,不需要额外的数组空间 缺点 只能对应一个串口,暂时没想到解决方案 */ //头文件中要包含 stdio.h 然后 ...
- 【Python】Async异步等待简单例子理解
import time def run(coroutine): try: print("") coroutine.send(None) except StopIteration a ...
- 离线安装paramiko
1. 利用yum下载paramiko依赖的rpm软件包 安装yum-utils yum -y install yum-utils yumdownloader python-setuptoolsyumd ...
- java 控制语句、数组、方法
一.控制语句 1.if 语句 if语句是指如果满足某种条件,就进行某种处理. 流程图: 2. if…else语句 语法格式: if (判断条件){ 执行语句1 …… }else{ 执行语句2 …… } ...
- 构建私有的verdaccio npm服务
用了很长一段时间的cnpmjs做库私有库,发现两个问题 1. 最开始是mysql对表情emoij的支持不好,但由于数据库没办法调整所以只好把第三方库都清掉,只留私有库 2. mac 上面cnpm in ...
- 预定义的 $_POST 变量用于收集来自 method="post" 的表单中的值
PHP $_POST 变量 在 PHP 中,预定义的 $_POST 变量用于收集来自 method="post" 的表单中的值. $_POST 变量 预定义的 $_POST 变量用 ...
- 使用 MySQLi 和 PDO 向 MySQL 插入数据
PHP MySQL 插入数据 使用 MySQLi 和 PDO 向 MySQL 插入数据 在创建完数据库和表后,我们可以向表中添加数据. 以下为一些语法规则: PHP 中 SQL 查询语句必须使用引号 ...