客户端在向zk服务器注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中。
当zk服务端触发Watcher事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象。来执行回调逻辑。
Watcher是一个标准的事件处理器,其定义了事件通知相关的逻辑,包含KeeperState和EventState,分别代表通知状态和事件类型,在回调process中进行处理。
public abstract void process(WatchedEvent event);
WatchedEvent类
public class WatchedEvent
{
//是一个枚举类,代表通知状态,是链接的,还是断开的。
keeperState;
//是一个枚举类,代表事件类型,当处于连接时,事件是子节点变化等事件
eventType
//节点路径
path
}
WatcherEvent类
WatcherEvent
{
//代表事件类型
type:int;
//代表节点状态
state:int;
path:String
}
总结:WatchedEvent和WatcherEvent表示的是同一个事物。WatchedEvent是一个逻辑事件,用于服务端和客户端程序执行过程中所需的逻辑对象。而WatcherEvent因为实现了序列化接口,因此,可以用于网络传输。
服务端在生成WatchedEvent事件之后,会调用getWrapper方法将自己包装成一个WatcherEvent,以便通过网络传输到客户端,客户端在接收到服务端的这个事件对象后,首先会将WatcherEvent事件还原成一个WatchedEvent事件,并传递给process方法进行处理。
从事件可以看到,客户端无法直接从该事件中获取到对应数据节点的原始数据内容以及变更后的新数据内容。
public class ZkWatchManager
{
dataWatches:Map>
existWatches: Map>
childWatches: Map>
defaultWatches: Watcher
}
举例:getData(String path,boolean watch,Stat stat)
getData(String path,Watcher watcher,Stat stat)
3. 在向getData接口注册Watcher后,客户端首先会对当前客户端请求request进行标记
request.setWatch(watcher != null);标记为使用watcher监听。
packet queuePacket(RequestHeader h,ReplayHeader r,Record request,Record response,AsyncCallback cb,String clientPath,String serverPath,Object ctx,WatchRegistraction registration)
{
Packet packet = null;
,,,
synchronized(outgoingQueue)
{
packet = new Packet(h,r,request,response,watchRegistration);
,,,
outgoingQueue.add(packet);
}
}
发送报文
zk客户端向服务端发送请求,同时等待请求的返回,把返回的结果注册到packet中的replayHeader r及record response中。
当接收到服务器的响应后,在finishPacket方法中把watcher真正的注册到ZkWatcherManager中。
private void finishPacket(Packet p)
{
if (p.watchRegistration != null)
{
p.watchRegistration.register(p.replayHeader.getErr());
}
}
WatchRegistration对象的register方法:
//根据返回的码注册到对应的set中
public void register(int rc)
{
if (shouldAddWatch(rc)
{
Map> watches = getWatches(rc);
,,,
watches.put(clientPath,watchers);
}
}
注:在底层网络传输过程中,并没有将WatchRegistration对象完全的序列化到底层字节数组中。
通过查看序列化源码:void createBB(),Packet的方法只会序列化:requestHeader和request,请求头和请求体。
getDataRequest.getWatch?cnxn:null
...
byte[] b = zks.getZkDatabase().getData(getDataRequest,getPath(),stat,getDataRequest.getWatch()?cnxn:null);
rsp = new GetDataResponse(b,stat);
break;
ServerCnxn代表一个客户端和服务端的链接。
是基于netty的实现;NettyServerCnxn,
ServerCnxn实现了Watcher的process接口
WatchManager
//watchTable是从数据节点的路径的粒度来托管Watcher。
--watchTable:HashMap>
//watch2Paths;是从Watcher的粒度来控制事件触发需要触发的数据节点。
--watch2Paths:HashMap>;
//定义对watcher的增删改查和触发watcher
triggerWatch();
addWatch();
removeWatch();
DataNode n = nodes.get(path);
setData(String path,byte[] data,int version,long zxid,long time)
{
DataNode n = nodes.get(path);
synchronized(n);
//触发节点通知
dataWatches.triggerWatch(path,EventType,NodeDataChanged);
}
//触发watcher实现
public set triggerWatch(String path,EventTye type ,Set process)
{
WatchedEvent e = new WatchedEvent(type,KeeperState,path);
HashSet watches;
synchronized(this)
{
watchers = watchTable.remove(path);
//...
for(Watcher w:watchers)
{
HashSet paths = watch2Paths..
if (paths != null)
{
paths.remove(path);
}
}
for(Watch w:watchers)
{
w.process(e);//进行处理
}
}
return watchers;
}
public class NIOServerCnxn extends ServerCnxn
{
synchronized void process (WathedEvent event)
{
ReplayHeader h = new ReplayHeader(-1,-1l,0);
,,,
watcherEvent e = event.getWrapper();
sendResponse(h,e,"notification");
}
}
在请求头中,标记为"-1",表面当前是一个通知。
将watched包装成watcherEvent,以便进行网络传输。
通过使用ServerCnxn对应的TCP链接向客户端发送一个WatcherEvent事件。
//SendThread接收事件通知
class SendThread extends Thread
{
void readResponse(ByteBuffer inbuffer)
{
if (replayHder.getXid()== -1)
{
//-1 means notification
//...
组装:WatcherEvent
WatcherEvent event = new WatherEvent();
//设置path,KeeperState,EventType
}
WatchedEvent we = new WatchedEvent(event);
eventThread.queueEvent(we);
return;
}
}
处理步骤:
1. 反序列化;将子节点流转换成WatcherEvent对象
2. 处理chrootPath,如果customer设置了chootPath,需要对服务端传过来的完整的节点路径进行chrootPath处理。如,chrootPath=/app/,那么对应的响应节点为:/app/locks,经过处理后就会变成一个相对路径:/locks,
3. 还原WatchedEvent
4. 回调Watcher,将watchedEvent对象交给EventThread线程,在下一轮周期中进行watcher回调。
EventThread处理事件通知:
EventThread
--waitingEvents:LinkedBlockingQueue
materialize的方法根据clientPath获取Set,客户端在识别出事件类型EventType后,会从相应的Watcher存储(dataWatches,existWatches或childWatches中)去除对应的Watcher。表明了客户端的Watcher机制也是一次性的触发失效的。WaitingEvents是一个待处理的watcher的队列,EventThread的run方法不断对该队列进行处理。
1.一次性
2.客户端串行执行(注意:不要因为一个Watcher的处理逻辑影响了整个客户端的Watcher回调)。
3.轻量:WatchedEvent只会包含三个部分;通知状态,事件类型和节点路径。