Zookeeper Watcher工作机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/asty9000/article/details/85493678

zookeeper的Watcher机制,可以概括为三个过程:客户端注册Watcher、服务端处理Watcher和客户端回调Watcher。

客户端注册Watcher

在创建zookeeper客户端对象实例时,可以向构造方法中传入一个默认的Watcher,这个Watcher将作为整个Zookeeper会话期间的默认Watcher,会一直被保存在客户端ZKWatchManager的defaultWatcher中。另外Zookeeper客户端也可以通过getData、getChildren和exists三个接口向Zookeeper服务器注册Watcher。

以getData为例,在向getData接口注册Watcher后,客户端首先会对当前客户端请求request进行标记,将其设置为“使用Watcher监听”,同时会封装一个Watcher的注册信息WatcherRegistration对象,用于暂时保存数据节点的路径和Watcher的对应关系。

在Zookeeper中,Packet可以被看作一个最小的通信协议单元,用于进行客户端与服务端之间的网络传输,任何需要传输的对象都需要包装成一个Packet对象。因此,在ClientCnxn中WatcherRegistration又会被封装到Packet中去,然后放入发送队列中等待客户端发送。

随后,客户端就会向服务端发送这个请求,同时等待请求的返回,完成请求发送后,会由客户端SendThread线程的readResponse方法负责接收来自服务端的响应。finishPacket方法会从Packet中取出对应的Watcher并注册到ZKWatchManager中去。在WatcherRegistration的register方法中,客户端会将之前暂时保存的Watcher对象转交给ZKWatchManager,并最终保存到dataWatches中去。dataWatches是一个Map<String,Set<Watcher>>类型的数据结构,用与将路径和Watcher对象进行一一映射后管理起来。

服务端处理Watcher

ServerCnxn

服务端收到来自客户端的请求后,在FinalRequestProcessor.processRequest()中会判断当前请求是否需要注册Watcher。

从getData请求的处理逻辑中可以看到,getDataRequest.getWatch()为true的时候,zookeeper就认为当前客户端请求需要进行Watcher注册,于是就会将当前的ServerCnxn对象和数据节点路径传入getData方法中。ServerCnxn是一个zookeeper客户端和服务器之间的连接接口,代表了一个客户端和服务器的连接。ServerCnxn的默认实现是NIOServerCnxn,3.4.0版本开始引入基于Netty的实现:NettyServerCnxn。数据节点的节点路径和ServerCnxn最终会被存储在WatchManager的watchTable和watch2Paths中。

WatchManager

WatchManager是zookeeper服务端Watcher的管理者,其内部管理的watchTable和watch2Paths两个存储结构,分别从两个维度对Watcher进行存储。watchTable是从数据节点路径的粒度来托管Watcher。watch2Paths是从Watcher的粒度来控制事件触发需要触发的数据节点。WatchManager还负责Watcher事件的触发,并移除那些已经被触发的Watcher。Watchmanager只是一个统称,在服务端,DataTree中会托管两个WatchManager,分别是dataWatches和childWatches,分别对应数据变更Watcher和子节点变更Watcher。

Watcher触发

在对指定节点进行数据更新后,通过调用WatchManager的triggerWatch方法来触发相关的事件。

1.封装WatchedEvent,首先将通知状态、事件类型以及节点路径封装成一个WatchedEvent对象。

2.查询Watcher,根据数据节点的节点路径从watchTable中取出对应的Watcher。如果没有找到,说明没有客户端在该数据节点上注册过Watcher,直接退出。如果找到,则会将其提取出来,同时会直接从watchTable和data2Paths中将其删除。

3.调用process方法来触发Watcher。在这一步中,会依次从步骤2找出的所有Watcher的process方法。对于需要注册Watcher的请求,会把当前请求对应的ServerCnxn作为一个Watcher进行存储。因此,这里调用的是ServerCnxn的process方法。

  • 在请求头中标记“-1”,表明当前是一个通知。
  • 将WatchedEvent包装成WatcherEvent,以便进行网络传输序列化。
  • 向客户端发送该通知。

 客户端回调Watcher

ClientCnxn.SendThread

对于来自服务器的响应,客户端都是由SendThread.readReponse方法来统一处理的,如果相应头replyHdr中标识了XID为“-1”,表明这是一个通知类型的响应,处理步骤大致为:

1.反序列化,首先将字节流转换成WatcherEvent对象。

2.处理chrootPath,如果客户端设置了chrootPath属性,那么需要对服务端传过来的完整的节点路径进行chrootPath处理,生成客户端的一个相对节点路径。

3.还原WatchedEvernt,将WatcherEvent对象转换成WatchedEvent对象。

4.回调Watcher,将WatchedEvent对象交给EventThread线程,在下一个轮询周期中进行Watcher回调。

ClientCnxn.EventThread

SendThread接收到服务端的通知事件后,会通过调用EventThread.queueEvent方法将事件传给EventThread线程。

1.queueEvent首先会根据该通知事件,从ZKWatchManager中取出所有相关的Watcher。

2.客户端在识别出事件类型EventType后,会从相应的Watcher存储(dataWatches、existWatches或childrenWatches)中去除对应的Watcher(此处使用的是remove接口,表明客户端的Watcher机制同样也是一次性的,触发后该Watcher就失效了)。

3.获取到相关的所有Watcher之后,会将其放入waitingEvents这个队列中去。waitingEvents是一个待处理的Watcher队列,EventThread的run方法会不断对该队列进行处理。

4.EventThread线程每次都会从waitingEvents队列中取出一个Wather,并进行串行同步处理。此处processEvent方法中的Watcher才是之前客户端真正注册的Watcher,调用其process方法就可以实现Watcher的回调了。

猜你喜欢

转载自blog.csdn.net/asty9000/article/details/85493678