在zookeeper中,引入了watcher机制来通知客户端,服务端的节点信息发生了变化。其允许客户端向服务端注册一个watcher监听,
当服务端的一些指定事件触发了这个watcher,就会向指定的客户端发送一个事件通知。
zookeeper的watcher机制主要包括客户端线程、客户端watchermanager和zookeeper服务器三部分。其具体工作流程为,
客户端在向zookeeper服务器注册watcher的同时,会将watcher对象存储在客户端的watchermanager中。当服务端触发watcher事件后,
会向客户端发送通知,客户端线程从watchermanager中取出对应的watcher对象,根据通知类型和节点路径,来处理回调逻辑,对客户端数据做出相应处理。
watch工作机制
zookeeper的工作机制主要包括三个步骤:客户端注册watcher、服务端处理watcher和客户端回调watcher事件。
首先需要了解一下zookeeper中的watchedevent结构,其包括三个基本属性:通知状态(keepstate)、事件类型(eventType)和节点路径(path)。
zookeeper使用watchedevent对象来封装服务端事件并传递给watcher,而实际传递的是watcherevent,其结构和watchedevent一样,
只是其实现了序列化,可以在网络上传输。从这里可以看出,watcher事件只是一个简单的事件说明,并不包含事件的数据变更内容,
这样能保证网络传输的高效性。
客户端注册watcher
在通过客户端接口调用时,可以指定一个watcher接口,以接受服务器事件的回调。在注册watcher即可后,客户端首先会对当前客户端请求request进行标记,
将其设置为“使用watcher监听”,同时会封装一个watcher的注册信息watchregistration对象,用于暂时保存数据数据节点和watcher的对象关系。
由于zookeeper中的最小通讯单元为packet,因此,在clientCnxn中watchregistration又会被封装到Packet中,然后放入发送队列中等待客户端发送。随后,
客户端会想服务端发送请求,并等待请求返回。完成请求之后,客户端的SendThread线程的readresponse方法负责接收服务端的请求,
并将接收到的packet中的watcher注册到ZKWatcherManager中,并最终保存到dataWatchers中,用于服务端事件的回调的时候获取对应节点的对应watcher事件。
服务端处理watcher
从上面的分析,我们可以知道,客户端注册的watcher并不会传递到服务端,只是在客户端进行了watcher的保存,所以服务端接受到watch注册事件之后,
需要将该事件进行封装,在服务端也进行保存。
ServerCnxn存储
我们知道ServerCnxn是服务端与客户端进行网络交互的一个接口,代表了客户端与服务端的连接。其底层采用netty实现。所以,在接受到注册请求之后,服务端会将ServerCnxn对象和数据阶段路径保存到WatchManager的watchTable和watch2Paths中。方便事件触发时的调用。
watcher触发
在对指定的节点发生相关的事件时,通过调用WatchManager的triggerWatch方法触发相关的事件。其通过将节点信息和事件类型进行封装成为watchedevent,
并查找到到对应节点的注册的watcher,然后分别调用watcher的回调函数process。而在process函数中其实就是通过封装的ServerCnxn
向客户端发送watchedevent数据请求。具体的watcher业务处理则在客户端处理。
客户端回调watcher
上面我们分析了,服务端并不处理watcher事件的具体逻辑,只是将事件通知发送到客户端。下面分析客户端是如何处理服务端发送回来的事件通知的。
SendThread接收事件通知
SendThread是客户端开启的与服务端建立TCP长连接的线程,它会一直保持连接状态,采用心跳监测的方式确保与服务端的连接存活。
在客户端的SendThread中,当接收到服务端请求之后,会将请求反序列化成watchedevent对象,并将watchedevent对象转换为watcherevent对象,
最后将watcherevent对象添加到EventThread线程,完成对watcher的回调。
EventThread是客户端的一个专门处理watcher时间的线程,其保持了一个待处理事件的队列。它根据传递的事件的类型和节点信息,
从客户端的ZKWatcherManager中取出相关的watcher,将其添加到EventThread事件队列中,并在去run方法中不断取出watcher事件进行处理。
但是,这里需要注意一点就是,(1)事件注册是一次性的,因为在每次处理事件之后,就会将相应的watcher注册删除;(2)客户端接收到的服务端watcher事件中
并不包含事件更改的具体内容,只是告知发生了这样一个watcher事件,所以,客户端在接收到watcher事件之后,需要在回调函数process中对服务端的数据进行重新获取,
才能获得更改的具体内容。