上一章中我们讨论了zkcli的普通请求流程,这次我们使用 get -w / 命令,看看上一章被忽略的流程
1、zkWatchManager
首先我们回顾一下zkcli连接的建立,最终的io都是通过clientCnxn对象完成,下面是它的构造函数:
public ClientCnxn(
String chrootPath,
HostProvider hostProvider,
int sessionTimeout,
ZKClientConfig clientConfig,
Watcher defaultWatcher,
ClientCnxnSocket clientCnxnSocket,
long sessionId,
byte[] sessionPasswd,
boolean canBeReadOnly
) throws IOException {
this.chrootPath = chrootPath;
this.hostProvider = hostProvider;
this.sessionTimeout = sessionTimeout;
this.clientConfig = clientConfig;
this.sessionId = sessionId;
this.sessionPasswd = sessionPasswd;
this.readOnly = canBeReadOnly;
this.watchManager = new ZKWatchManager(
clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET),
defaultWatcher);
this.connectTimeout = sessionTimeout / hostProvider.size();
this.readTimeout = sessionTimeout * 2 / 3;
this.sendThread = new SendThread(clientCnxnSocket);
this.eventThread = new EventThread();
initRequestTimeout();
}
其中会注册一个WatchManager,这个类主要是管理不同监听事件与处理逻辑,默认的Watcher为传入的defaultWatcher,其是下面这个默认实现
private class MyWatcher implements Watcher {
public void process(WatchedEvent event) {
if (getPrintWatches()) {
ZooKeeperMain.printMessage("WATCHER::");
ZooKeeperMain.printMessage(event.toString());
}
if (connectLatch != null) {
// connection success
if (event.getType() == Event.EventType.None
&& event.getState() == Event.KeeperState.SyncConnected) {
connectLatch.countDown();
}
}
}
}
2、发送流程注册Watch
这里采用 getData做测试,进入zookeeper对象发包流程
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 wcb = null;
if (watcher != null) {
//若有-w标志则会创建一个watchRegistration对象--标志监听类型、路径与watcher
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.setWatch(watcher != null);
GetDataResponse response = new GetDataResponse();
//组装请求包
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();
}
//组包的构造调用,可以看到最终,该监听与packet进行了绑定
Packet packet = queuePacket(
h,
r,
request,
response,
null,
null,
null,
null,
watchRegistration,
watchDeregistration);
根据上一章发送流程,我们现在去异步读包的地方:
protected void finishPacket(Packet p) {
int err = p.replyHeader.getErr();
if (p.watchRegistration != null) {
//若packet收包有wacther则将其添加进WatchManager
p.watchRegistration.register(err);
}
...
在这里就完成了客户端监听事件的注册流程。
3、接收流程回调Watcher
而消息读取之后会生成对应的event来异步执行,对于watcher事件为以下逻辑
private void processEvent(Object event) {
try {
if (event instanceof WatcherSetEventPair) {
// each watcher will process the event
WatcherSetEventPair pair = (WatcherSetEventPair) event;
for (Watcher watcher : pair.watchers) {
try {
//这里回调了注册的函数,默认的为上面MyWatch实现--打印消息
watcher.process(pair.event);
} catch (Throwable t) {
LOG.error("Error while calling watcher.", t);
}
}
...
4、流程总结
可以看到zkcli的watch逻辑还是比较简单,总的来讲就是观察者模式的实现,为不同处理事件注册对应的回调函数,当服务端触发事件发送消息,客户端接收到消息进行回调处理。
到这里,zkcli的主要逻辑我们已经分析完全,下一章我们开始进行单机版zkserver的学习。