Watcher是实现ZooKeeper的发布/订阅功能最核心的一个角色。当我们需要对ZooKeeper的某一个节点的变化做出后续处理时,就需要使用到Watcher。
ZooKeeper的Watcher机制,总的来说可以分为三个流程:client注册Watcher,server处理Watcher,client回调Watcher。接下来我将从上面三个流程分析Watcher是如何工作的。
Client注册Watcher
Client中有一个ZKWatcher Manager用来保存所有注册的Watcher 。
1.使用默认的构造函数注册:
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)
这个Watcher 会作为整个会话期间的默认的Watcher,保存在defaultWathcer。
2.使用getData、exist方法注册,这里以getData为例子进行分析,getData有两个重载的方法:
public byte[] getData(String path, boolean watch, Stat stat)
这个boolean 变量表示是否注册默认的Watcher
public byte[] getData(final String path, Watcher watcher, Stat stat)
public byte[] getData(final String path, Watcher watcher, Stat stat)
throws KeeperException, InterruptedException
{
final String clientPath = path;
PathUtils.validatePath(clientPath);
// the watch contains the un-chroot path
//WatchRegistration 用来保存Watch和path的对应关系
WatchRegistration wcb = null;
if (watcher != null) {
wcb = new DataWatchRegistration(watcher, clientPath);
}
final String serverPath = prependChroot(clientPath);
RequestHeader h = new RequestHeader();
h.setType(ZooDefs.OpCode.getData);
GetDataRequest request = new GetDataRequest();
request.setPath(serverPath);
//对于request请求进行标记,表示此请求需要注册watcher
request.setWatch(watcher != null);
GetDataResponse response = new GetDataResponse();
//设置ReplyHeader
ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
if (r.getErr() != 0) {
throw KeeperException.create(KeeperException.Code.get(r.getErr()),
clientPath);
}
if (stat != null) {
DataTree.copyStat(response.getStat(), stat);
}
return response.getData();
}
WatchRegistration 用来保存Watch和path的对应关系的。在ZooKeeper中,最小的通信单元是Packet,所以需要把WatchRegistration用Packet进行包装。包装成Packet以后,放入队列中等待发送。
Client发送这个Packet以后,由SendThread线程的readResponse()来接受响应,然后调用finishPacket(packet)方法从packet中提出Wathcer,并且注册到ZKWatcherManager中。
private void