Watcher是实现ZooKeeper的发布/订阅功能最核心的一个角色。当我们需要对ZooKeeper的某一个节点的变化做出后续处理时,就需要使用到Watcher。
ZooKeeper的Watcher机制,总的来说可以分为三个流程:client注册Watcher,server处理Watcher,client回调Watcher。接下来我将从上面三个流程分析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 finishPacket(Packet p) {
if (p.watchRegistration != null) {
//注册watcher
p.watchRegistration.register(p.replyHeader.getErr());
}
。。。
}
WatchRegistration.register方法。
//取Wathcer,并且注册到ZKWatcherManager中。
public void register(int rc) {
if (shouldAddWatch(rc)) {
Map> watches = getWatches(rc);
synchronized(watches) {
Set watchers = watches.get(clientPath);
if (watchers == null) {
watchers = new HashSet();
watches.put(clientPath, watchers);
}
watchers.add(watcher);
}
}
}
客户端注册Watcher ,并不是真正的把完整的Watcher 进行注册,而仅仅是将RequestHeader和requst两个属性进行网络传输。
在服务端进行最后一步请求的处理的时,FinalRequestProcessor.processRequest()方法会判断此时的请求是否需要注册Watcher 。
case OpCode.getData: {
...
//判断此时的请求是否需要注册Watcher 。
byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
getDataRequest.getWatch() ? cnxn : null);
...
}
如果需要注册,那么就把对应了Watcher 的ServerCnxn以及path存储在WatchManager中。
通过上面两步,Watcher 以及存在于client以及Server端了,那么Watcher 如何触发,以使得client回调呢?
我们进入WatcherManager的triggerWatch方法一探究竟。
public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
//封装WatchedEvent
WatchedEvent e = new WatchedEvent(type,
KeeperState.SyncConnected, path);
HashSet<Watcher> watchers;
synchronized (this) {
watchers = watchTable.remove(path);
if (watchers == null || watchers.isEmpty()) {
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG,
ZooTrace.EVENT_DELIVERY_TRACE_MASK,
"No watchers for " + path);
}
return null;
}
//根据Path查询Watcher
for (Watcher w : watchers) {
HashSet<String> paths = watch2Paths.get(w);
if (paths != null) {
//删除Watcher ,也就是说Watcher 是一次性的
paths.remove(path);
}
}
}
for (Watcher w : watchers) {
if (supress != null && supress.contains(w)) {
continue;
}
//调用process来真正的触发Watcher
w.process(e);
}
return watchers;
}
我们看一下process()方法做了什么。
abstract public void process(WatchedEvent event);
watcher的process是一个abstract方法。我们知道ServerCnxn是implemnt了Watcher接口的,而NIOServerCnxn又实现了ServerCnxn。所以看一下NIOServerCnxn中的process
synchronized public void process(WatchedEvent event) {
//请求头标-1,表示这是一个通知
ReplyHeader h = new ReplyHeader(-1, -1L, 0);
if (LOG.isTraceEnabled()) {
ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK,
"Deliver event " + event + " to 0x"
+ Long.toHexString(this.sessionId)
+ " through " + this);
}
// Convert WatchedEvent to a type that can be sent over the wire
//包装成WatcherEvent ,以便于用来网络传输
WatcherEvent e = event.getWrapper();
//向客户端发送请求
sendResponse(h, e, "notification");
}
由此可知,process并不是真正执行Watcher的业务方法的,而是起到一个通知的作用。真正执行回调业务逻辑的在Client端。
Client端是通过ClientCnxn来管理客户端底层的网络通信的。而内部类SendThread是用来进行消息的接受以及发送。
SendThread.readRespose(),我截取了其中的一段代码
if (replyHdr.getXid() == -1) {
// -1 means notification
//-1表明这是一个通知,与上面的process对应
if (LOG.isDebugEnabled()) {
LOG.debug("Got notification sessionid:0x"
+ Long.toHexString(sessionId));
}
WatcherEvent event = new WatcherEvent();
//反序列化成WatcherEvent
event.deserialize(bbia, "response");
// convert from a server path to a client path
//处理chrootPath ,相对路径变为绝对路径
if (chrootPath != null) {
String serverPath = event.getPath();
if(serverPath.compareTo(chrootPath)==0)
event.setPath("/");
else
event.setPath(serverPath.substring(chrootPath.length()));
}
//WatcherEvent变成WatchedEvent
WatchedEvent we = new WatchedEvent(event);
if (LOG.isDebugEnabled()) {
LOG.debug("Got " + we + " for sessionid 0x"
+ Long.toHexString(sessionId));
}
//将WatchedEvent 交给eventThread进行处理
eventThread.queueEvent( we );
return;
}
由此可知,SendThread只是负责接受通知,真正的Watcher执行回调业务是在eventThread中。
EventThread.queueEvent
public void queueEvent(WatchedEvent event) {
if (event.getType() == EventType.None
&& sessionState == event.getState()) {
return;
}
sessionState = event.getState();
// materialize the watchers based on the event
//找到此WatchedEvent相关所有watchers
WatcherSetEventPair pair = new WatcherSetEventPair(
watcher.materialize(event.getState(), event.getType(),
event.getPath()),
event);
// queue the pair (watch set & event) for later processing
//放入waitingEvents队列中
waitingEvents.add(pair);
}
找到此WatchedEvent相关所有watchers以后,会把watchers放入waitingEvents队列中,EventThread的run()会不断从队列拿出任务执行。
EventThread.run()
@Override
public void run() {
try {
isRunning = true;
while (true) {
Object event = waitingEvents.take();
if (event == eventOfDeath) {
wasKilled = true;
} else {
//执行event
processEvent(event);
}
......
}
private void processEvent(Object event) {
try {
if (event instanceof WatcherSetEventPair) {
// each watcher will process the event
WatcherSetEventPair pair = (WatcherSetEventPair) event;
//找出所有的Watcher,然后一个一个执行process方法
//这里才是真正的执行了回调的业务逻辑
for (Watcher watcher : pair.watchers) {
try {
watcher.process(pair.event);
} catch (Throwable t) {
LOG.error("Error while calling watcher ", t);
}
}
....
1.客户端有一个ZKWatcherManager用来存储注册的Watcher
2.客户端发起一个带有注册Watcher的请求以后,首先会把此请求标记为需要注册Watcher,然后封装为Packet,发送给服务端
3.服务端接收到请求以后,读取到需要注册Watcher,那么就会用一个类似Watcher的ServerCnxt放入WatcherManager中去。
4.当Watcher对应的节点发生了Event事件,服务端就会发送一个通知,告诉客户端path发生了XXXEvent。
5.客户端收到通知以后(SendThread来负责接受通知),找出所有的相关的Watcher,并包装为WatcherSetEventPair,放入waiting队列。
6.客户端的EventThread执行Run方法,从waiting取出WatcherSetEventPair,找到所有的Watcher进行真正的回调业务的执行。