准备工作
经过上一小节的学习我们知道ZookeeperServerMain是单机版的服务器主类
我们可以自己写一个ZkClient类
public class ZkClient {
public static void main(String[] args) throws IOException, KeeperException, InterruptedException {
final CountDownLatch countDownLatch=new CountDownLatch(1);
ZooKeeper zooKeeper=
new ZooKeeper("localhost:2182",
4000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println(event);
if(Event.KeeperState.SyncConnected==event.getState()){
//如果收到了服务端的响应事件,连接成功
countDownLatch.countDown();
}
}
});
countDownLatch.await();
//CONNECTED
System.out.println(zooKeeper.getState());
byte[] data = zooKeeper.getData("/lry",new Watcher(){
@Override
public void process(WatchedEvent event) {
System.out.println(event);
}
},new Stat());
System.out.println(new String(data));
System.in.read();
//只有getData,getChildren,exist这三个api有watcher
}
}
我们还需要一个控制台可以输入命令的主类,ZookeeperMain
进入ZookeeperMain的run方法,把如下代码注释
// String line;
// Method readLine = consoleC.getMethod("readLine", String.class);
// while ((line = (String)readLine.invoke(console, getPrompt())) != null) {
// executeLine(line);
// }
并且加上一行代码
这样先启动ZookeeperServerMain,在启动ZookeeperMain我们就可以在控制台手打命令了
watch示例
菜鸟上说的很简洁明了,但是有一些小点限于篇幅没有说到,比如:
- 客户端发送给服务器的包不包括watcher对象信息?
- pendingQueue在watch机制中的作用?
- 为什么只需要request.setWatch(true),而不需要把watcher对象发送给服务器?
- 服务器在收到修改命令后触发triggerWatch方法发送xid=-1的事件给客户端后,客户端是如何从pendingQueue拿出watcher对象,然后回调process方法的?
源码解析
ps: 为了截图和阅读方便,针对源码有所改动
客户端getData之后发送数据流程
wcb在packet里,packet在outgoingQueue里,ClientCnxn的内部类SendThread的run方法把outgoingQueue给了ClientCnxnSocket类ClientCnxnSocketNio继承ClientCnxnSocket,所以最终ClientCnxnSocketNio负责从outgoingQueue取出packet发送部分数据给服务端。
ClientCnxn的内部类SendThread的run方法 的 doTranport() —>ClientCnxnSocketNio.doTranport()---->ClientCnxnSocketNio.doIO()
下面看看服务器收到这条消息会做什么
有了前一篇博客的铺垫,我们直接进入FinalRequestProcessor,找到processRequest方法的case getData
到此服务器就把getData需要的数据返回给了客户端(这里我们不去看客户端接受这个数据的过程),服务端还把watch保存到map里面去了,下面我们看看服务器收到修改命令触发watch的流程
同样是FinalRequestProcessor的processRequest方法
最终会调用到DataTree的processTxn方法
至此服务器的watch就解析完毕,接下来看看客户端接到这个nodechanged事件是如何处理的
ClientCnxn的SendThread线程负责接受和发送数据,最终会调用到ClientCnxnSocketNIO的doIO方法
再进入ClientCnxn的readResponse的case xid=-1
看到这幅图其实有点奇怪,因为我们源码分析到现在都没有看到dataWatches这个map是如何把path对应的wathcer put进去的,其实这是getData后服务器返回数据给客户端,这跟客户端接受数据流程有关,我待会再附加里说这个吧。
但是除此之外,watcher就到waitingEvents队列里了,我们看看ClientCnxn的run方法
附加:说明pendingQueue作用和 客户端的ZKWatchManager类的dataWatches, childWatches, existsWatches是怎么put watcher的
接受数据地方:
ClientCnxn的SendThread的readResponse方法
完整的packet是从pendingQueue拿到的,也是它保存了watcher信息,因为watcher并没有在服务器和客户端之前传送
真正往dataWatches put<path,Watcher>
总结
watch机制确实绕来绕去的,一共要绕四次,客户端两次,服务端两次,zookeeper3.6提供了持久化watch和递归持久化watch,其中持久化做法其实非常简单
remove改成get即可,源码也确实是这么做的,还添加了一个addWatch api,可以对不同的操作watch