watcher的特征
zookeeper的一大特点之一就是watch机制,所有基于zookeeper的消息通知机制基本上都是建议在watcher机制之上的,比如业界比较有名的由360公司开源的Qconf的底层就是基于这个实现的,本章其实就是想将zookeeper的watcher的数据存储、watcher的添加、watcher的触发整个过程讲解清楚,方便大家有个清晰的认识。

watcher的存储

说明:
watcher主要分为dataWatches和childWatches,其中dataWatches是保存节点层面的watcher对象的,childWatches是保存子节点层面的watcher对象的。核心的点在于WatchManager数据结构。

说明:
WatchManager内部主要维持了watchTable(path->watcher)以及watch2Paths(watcher->path)两个映射关系,其实这也就是代表了一个path有多个watcher,或一个watcher同时watch了多个path的场景。
整个watcher的添加过程其实同时需要修改watchTable和watch2Paths两个map对象,具体的修改操作可以参考上面的代码贴图。
watcher的添加
zookeeper的watcher的添加一般通过三个途径,分别是通过exists、getData、getChildren三种方式添加,其中exists用于判断节点是否存在,getData获取节点数据,getChildren获取子节点。其实从这个地方我们可以看出来其实watcher的添加其实是附加动作,基本上都是在主动作的同时添加一下watcher。
从下面的API我们其实就看出来了,对应上面提到的3个动作提供3个接口用于添加watcher动作。

exists添加watcher

说明:
直观的感受就是我们在执行exists的api的时候,我们最终通过statNode的接口保存了watcher,注意这里保存在dataWatches当中。
getData添加watcher

说明:
直观的感受就是我们再执行getData的时候,我们最终通过addWatch的接口保存了watcher,注意这里保存在dataWatches当中。
getChildren添加watcher

说明:
直观的感受就是我们再执行getData的时候,我们最终通过addWatch的接口保存了watcher,注意这里的保存在childWatches当中。
watcher添加过程

说明:
其实数据是在FinalRequestProcessor当中进行写入的,根据OpCode执行的写入操作,所以基本上我认为通过这个图基本上能够了解到watcher写入的入口了。
watcher的触发

说明:
从这里我们可以看出来并且印证一个观点,那就是watcher是一次性触发的,原因就在于每次我们triggerWatch的时候我们都会把watcher从watchTable当中移除,根据具体触发的path路径找到对应的watcher,然后移除watcher并触发watcher的后续动作。注意这里触发了watcher。

说明:
从这里我们可以看出来,触发watcher的几个动作主要包括创建节点(createNode)、删除节点(deleteNode)、数据变更(setData)。

说明:
从上图可以看出来我们在创建节点的时候会同时判断是否触发dataWatches和childWatches,这也就解释了childWatches是如何被触发的。