ZooKeeper源码解析(10)-Watcher实现

Posted by AlstonWilliams on February 17, 2019

我们知道,ZooKeeper有一个非常重要的功能,就是做分布式锁.而这个分布式锁,就是通过ZooKeeper的Watcher来实现的.

在这篇文章中,我们将会介绍,在ZooKeeper中,Watcher到底是如何实现的.

ZooKeeper Watch介绍

在ZooKeeper中,有三个读操作:getData(), getChildren(), exists(),都接受一个叫做Watch参数.

在ZooKeeper中,有两种Watch,分别是data watcheschild watchesgetData()和exists()会设置data watches,而getChildren()会设置child watches

Watch是由服务器端维护的一个集合,它会为每个客户端维护这么一个集合.

setData()会触发对应的znode上的data watchescreate()会触发对应znode的data watches以及它的父节点的child watchesdelete()会触发对应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)部分的实现.

从这个方法中,我们可以这个一段代码块:

我们可以看到,其中调用了ZKDatabasegetData()方法来设置获取数据.并且最后有一个关于Watch的参数.

而我们上面正好介绍过,在使用getData()时,可以设置Watch

我们跟进去,可以发现它最后就是调用了DataTreegetData()方法.

我们可以看到,这儿只不过是记录了一下这个Watch

这里读者们可能就有一个疑问,就是,之前我们说过,ZooKeeper为每一个客户端维护一个Watch集合,但是这里我们只看到直接就放到了DataTree这一个共享的数据结构里了呀.

这是为什么呢?

因为Watch的实现NIOServerCnxn天生就是客户端独立的呀.

Watch是怎么被消费掉,怎么被触发的呢?

我们回到FinalRequestProcessorprocessRequest()方法,可以看到这么一段代码:

其中,最重要的就是图中我们圈出来的那一行.

我们看一下在zks.processTxn()的实现.

其中这个方法中,最重要的还是我们圈出来的那一行.

我们再跟进去,发现它调用了DataTreeprocessTxn()方法.我们直接来看这个方法的实现.

我们只分析OpCode.create时的情况.

我们看到,就是调用了createNode()方法.

在这个方法的尾部,我们可以看到,就触发了dataWatcheschildWatches

就这样完成了Watch的触发.