我们知道,ZooKeeper有一个非常重要的功能,就是做分布式锁.而这个分布式锁,就是通过ZooKeeper的Watcher来实现的.
在这篇文章中,我们将会介绍,在ZooKeeper中,Watcher到底是如何实现的.
ZooKeeper Watch介绍
在ZooKeeper中,有三个读操作:getData(), getChildren(), exists(),都接受一个叫做Watch参数.
在ZooKeeper中,有两种Watch,分别是data watches和child watches,getData()和exists()会设置data watches,而getChildren()会设置child watches.
Watch是由服务器端维护的一个集合,它会为每个客户端维护这么一个集合.
setData()会触发对应的znode上的data watches,create()会触发对应znode的data watches以及它的父节点的child watches,delete()会触发对应znode的data watches以及child watches,以及其父节点的child watches.
Watches只会被触发一次.
更多关于Watch的介绍,请到这里
ZooKeeper中Watch的实现
在ZooKeeper中,有一个Watcher接口:
这个Watcher接口中,定义了一个方法:process(WatchedEvent event).
就是这个方法,定义了当事件被触发时,要进行的操作.
而实现Watcher接口的类,有三个,都是我们很熟悉的:
- ServerCnxn
- NIOServerCnxn
- NettyServerCnxn
我们这里主要看一下NIOServerCnxn中的实现:
我们可以看到,其process(WatchEvent event)方法,就是将WatchEvent发送给客户端.
ZooKeeper中Watch的原理
在之前的文章中,我们介绍请求处理的时候,提到了有一个生产链似的请求处理方式,其中最后的一个RequestProcessor就是FinalRequestProcessor,而正是在这个FinalRequestProcessor中,设置了Watch.
我们来看一下FinalRequestProcessor中的processRequest(Request request)部分的实现.
从这个方法中,我们可以这个一段代码块:
我们可以看到,其中调用了ZKDatabase的getData()方法来设置获取数据.并且最后有一个关于Watch的参数.
而我们上面正好介绍过,在使用getData()时,可以设置Watch.
我们跟进去,可以发现它最后就是调用了DataTree的getData()方法.
我们可以看到,这儿只不过是记录了一下这个Watch.
这里读者们可能就有一个疑问,就是,之前我们说过,ZooKeeper为每一个客户端维护一个Watch集合,但是这里我们只看到直接就放到了DataTree这一个共享的数据结构里了呀.
这是为什么呢?
因为Watch的实现NIOServerCnxn天生就是客户端独立的呀.
那Watch是怎么被消费掉,怎么被触发的呢?
我们回到FinalRequestProcessor的processRequest()方法,可以看到这么一段代码:
其中,最重要的就是图中我们圈出来的那一行.
我们看一下在zks.processTxn()的实现.
其中这个方法中,最重要的还是我们圈出来的那一行.
我们再跟进去,发现它调用了DataTree的processTxn()方法.我们直接来看这个方法的实现.
我们只分析OpCode.create时的情况.
我们看到,就是调用了createNode()方法.
在这个方法的尾部,我们可以看到,就触发了dataWatches和childWatches.
就这样完成了Watch的触发.