watcher监听机制是Zookeeper中非常重要的特性,我们基于zookeeper 上创建的节点,可以对这些节点绑定监听事件。比如可以监听节点数据的变更、节点删除、子节点状态变更等事件。通过这个事件机制,可以基于zookeeper实现分布式锁,集群管理等功能。
事件特性
当数据发生变化时,zookeeper 会产生一个watcher 事件,并且会发送到客户端,但是客户的只会收到一次通知。如果后续这个节点再次发生变化,那么之前设置 watcher 的客户端不会再次收到消息。即watcher是一次性的操作。可以通过循环监听去达到永久监听的效果
注册事件
通过这三个操作来绑定事件:
- getData
- exists
- getChildren
凡是事务类型的操作,都会触发监听事件。(create/delete/setData)
事件类型
事件类型 | 发生原因 |
---|---|
None(-1) | 客户端连接状态发生变化的时候,会收到none的事件 |
NodeCreated(1) | 创建节点的事件。比如 create /demo |
NodeDeleted(2) | 删除节点的事件 。比如 delete /demo |
NodeDataChanaged(3) | 节点数据发生变更。 |
NodeChildrenChanged(4) | 子节点被创建、删除会触发事件 |
事件操作类型对应事件机制
操作类型 | /demo 监听事件 (exists/getData/getChild) | /demo/chindren |
---|---|---|
create(/demo) | NodeCreated (exists/getData) | 无 |
delete(/demo) | NodeDeleted (exists/getData) | 无 |
setData(/demo) | NodeDataChanaged (exists/getData) | 无 |
create(/demo/children) | NodeChildChangerend (getChild) | NodeCreated |
delete(/demo/children) | NodeChildChangerend (getChild) | NodeDeleted |
setData(/demo/children) | NodeDataChanaged |
客户端进行绑定 watch 事件, 服务器怎么知道是哪个客户端的呢?
客户端 注册 watch 事件, 将事件放到 outgoingQueue 队列中,
SendThread 线程会从 outgoingQueue 队列中拿出数据包发送到zk server,
客户端传入数据到 zk server 上注册 watch 事件 :path : /path , watcher : true
服务器里存储了对应的 path 和 path 对应的 watch 事件
// path ,对应的客户端的网络处理类
HashMap<String,HashSet<Watcher>> watchTable;
客户端本地会记录所有的 watch 事件
private final Map<String, Set<Watcher>> dataWatches =
new HashMap<String, Set<Watcher>>();
private final Map<String, Set<Watcher>> existWatches =
new HashMap<String, Set<Watcher>>();
private final Map<String, Set<Watcher>> childWatches =
new HashMap<String, Set<Watcher>>();
服务端记录了 每个 path 对应的 所有的 watch 事件的集合
- 每个 watch 里存储的是客户端的 网络处理类。
- 服务端可以通过 watch 的网络处理类来与客户端进行通信
客户端记录了每个 path 下的所有 watch 事件的集合
- 每个 watch 里存储的是真实的 watcher 类
客户端通过传入一个 path 和 watch=true ,在服务端注册一个事件的监听 watchTable 并服务端返回 stat ,客户端接收到响应之后在本地注册事件 dataWatches ,记录了path 和 watch 实现类
当监听的 path 存在 create 、 delete 、 setData 操作时,服务端会通过记录的 watch (客户端的网络处理类)发送一个事件给客户端, 客户端收到之后通过 path 找到具体的 watch 处理类,调用 process 方法进行调用。
Watcher 事件源码分析
注册事件,发送数据到服务端
- 构造Zookeeper客户端,客户端注册Watcher 事件,并发送数据给服务端
//构造一个Zookeeper的方法,传入一个Watcher事件,
ZooKeeper zooKeeper = new ZooKeeper("192.168.45.131:2181", 4000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(Event.KeeperState.SyncConnected==event.getState()){
//如果是连接响应
countDownLatch.countDown();
}
}
});
//绑定一个exists watcher事件
zooKeeper.exists("/demo", new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("内部watcher:"+event.getType()+":"+event.getPath());
}
});
源码的实现
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher,
boolean canBeReadOnly) throws IOException {
//将构造时传过来的watcher作为watcherManage的默认watcher
watchManager.defaultWatcher = watcher;
ConnectStringParser connectStringParser = new ConnectStringParser(
connectString);
HostProvider hostProvider = new StaticHostProvider(
connectStringParser.getServerAddresses());
//初始化ClientCnxn,并实例化SendThread 和 EventThread 两个内部类线程
cnxn = new ClientCnxn(connectStringParser.getChrootPath(),
hostProvider, sessionTimeout, this, watchManager,
getClientCnxnSocket(), canBeReadOnly);
//调用启动方法,启动sendThread.start();eventThread.start(); 两个线程
cnxn.start();
}
至此客户端进行 zookeeper 的构造结束。
- 下面是 exists 的实现
public Stat exists(final String path, Watcher watcher) {
final String clientPath = path;
PathUtils.validatePath(clientPath);
// the watch contains the un-chroot path
WatchRegistration wcb = null;
if (watcher != null) {
//对 watcher 进行绑定注册
wcb = new ExistsWatchRegistration(watcher, clientPath);
}
final String serverPath = prependChroot(clientPath);
//封装请求头,声明 请求类型是 exists
RequestHeader h = new RequestHeader();
h.setType(ZooDefs.OpCode.exists);
//封装 exists 的请求信息,将 节点path 和是否存在watcher 进行封装
ExistsRequest request = new ExistsRequest();
request.setPath(serverPath);
request.setWatch(watcher != null);
// 设置服务端响应的接收类
SetDataResponse response = new SetDataResponse();
//将 requestHeader、existsRequest、setDataResponse、watchRegistration 提交到发送队列中去
//此时的 cnxn 便是构造 zookeeper 时的 ClientCnxn 对象
ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
//返回exists 得到的结果信息 stat 信息
return response.getStat().getCzxid() == -1 ? null : response.getStat();
}
进入到 ClientCnxn 的 submitRequest 方法中
public ReplyHeader submitRequest(RequestHeader h, Record request,
Record response, WatchRegistration watchRegistration)
throws InterruptedException {
ReplyHeader r = new ReplyHeader();
//将消息添加到队列,并构造一个Packet 传输对象
//组装数据包,packet 是客户端和服务端进行通信的最小单元
Packet packet = queuePacket(h, r, request, response, null, null, null,
null, watchRegistration);
synchronized (packet) {
while (!packet.finished) {
//在数据包没有处理完成之前,一致阻塞
packet.wait();
}
}
return r;
}
Packet queuePacket(RequestHeader h, ReplyHeader r, Record request,
Record response, AsyncCallback cb, String clientPath,
String serverPath, Object ctx, WatchRegistration watchRegistration){
Packet packet = null;
// Note that we do not generate the Xid for the packet yet. It is
// generated later at send-time, by an implementation of ClientCnxnSocket::doIO(),
// where the packet is actually sent.
synchronized (outgoingQueue) {
// 将相关传输对象装换成Packet
packet = new Packet(h, r, request, response, watchRegistration);
packet.cb = cb;
packet.ctx = ctx;
packet.clientPath = clientPath;
packet.serverPath = serverPath;
if (!state.isAlive() || closing) {
conLossPacket(packet);
} else {
// If the client is asking to close the session then
// mark as closing
if (h.getType() == OpCode.closeSession) {
closing = true;
}
//添加到 队列中,
// LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列,
//即可以从队列的两端插入和移除元素。
// 双向队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。
// LinkedList类是双向列表
outgoingQueue.add(packet);
}
}
//此处是多路复用机制,唤醒Selector ,告诉他有数据包添加过来了
sendThread.getClientCnxnSocket().wakeupCnxn();
return packet;
}
- SendThread 的发送过程
在初始化连接的时候,zookeeper初始化了两个线程并且启动了。接下来是分析SendThread 的发送过程,因为是一个线程,所以会调用run方法
//SendThread#run 方法中,这个调用最为重要,
//调用 clientCnxnSocket 发起传输,进入到ClientCnxnSocketNIO.doTransport 方法中
//其中 pendingQueue 是一个用来存放已经发送、等待回应的 packet 队列
//outgoingQueue 是 exists 中将事件通过封装成 Packet 添加到该队列中
clientCnxnSocket.doTransport(to, pendingQueue, outgoingQueue, ClientCnxn.this);
总结:client 调用 exists 注册监听以后,会做几个事情:
- 将请求数据封装为 packet ,添加到 outgoingQueue
- SendThread 线程会执行数据发送操作,主要是将 outgoingQueue 队列中的数据发送到服务端
- 通过ClientCnxnSocket.doTransport ,其中ClientCnxnSocket 只是zookeeper 客户端和服务端连接通信的封装。
- 最后在ClientCnxnSocketNIO.doIO()方法中通过
sock.write(p.bb);
将请求的数据包发送到服务端
服务端接受请求处理流程
-
从服务端NettyServerCnxn类 ,开始分析
服务端有个NettyServerCnxn 类,用来处理客户端发送过来的请求
//服务端接受客户端发来的数据 NettyServerCnxn
public void receiveMessage(ChannelBuffer message) {
try {
while(message.readable() && !throttled) {
//byteBuffer 不为空
if (bb != null) {
//已经读完
if (bb.remaining() == 0) {
if (initialized) {
// 处理客户端传过来的数据包
zks.processPacket(this, bb);
}
}
}
}
} catch(IOException e) {
LOG.warn("Closing connection to " + getRemoteSocketAddress(), e);
close();
}
}
//ZooKeeperServer
public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) {
if (h.getType() == OpCode.auth) {
} else {
if (h.getType() == OpCode.sasl) {
}else {
//不是权限也不是sasl,组装请求参数
Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(),
h.getType(), incomingBuffer, cnxn.getAuthInfo());
si.setOwner(ServerCnxn.me);
//提交请求
submitRequest(si);
}
}
cnxn.incrOutstandingRequests(h);
}
public void submitRequest(Request si) {
try {
touch(si.cnxn);
boolean validpacket = Request.isValid(si.type);
if (validpacket) {
//访问调用链中的方法
firstProcessor.processRequest(si);
if (si.cnxn != null) {
incInProcess();
}
}
} catch (MissingSessionException e) {
if (LOG.isDebugEnabled()) {
LOG.debug("Dropping request: " + e.getMessage());
}
} catch (RequestProcessorException e) {
LOG.error("Unable to process request:" + e.getMessage(), e);
}
}
此处的firstProcessor.processRequest(si); 是一个调用链,查看源码 找到实例化的过程
/**
* 封装了三个调用链:
* PrepRequestProcessor(SyncRequestProcessor(FinalRequestProcessor()))
*/
protected void setupRequestProcessors() {
RequestProcessor finalProcessor = new FinalRequestProcessor(this);
RequestProcessor syncProcessor = new SyncRequestProcessor(this,
finalProcessor);
//此处和下面启动了两个线程,
//所以在processRequest 方法执行完后,会用到线程异步去执行
((SyncRequestProcessor)syncProcessor).start();
firstProcessor = new PrepRequestProcessor(this, syncProcessor);
((PrepRequestProcessor)firstProcessor).start();
}
所以了解了调用过程,接下来 `firstProcessor.processRequest(si);` 实际上是调用 PrepRequestProcessor.processRequest() 方法
//PrepRequestProcessor
public void processRequest(Request request) {
// request.addRQRec(">prep="+zks.outstandingChanges.size());
// 添加到阻塞队列里之后,会在 run 方法中进行处理
submittedRequests.add(request);
}
public void run() {
try {
while (true) {
Request request = submittedRequests.take();
long traceMask = ZooTrace.CLIENT_REQUEST_TRACE_MASK;
if (request.type == OpCode.ping) {
traceMask = ZooTrace.CLIENT_PING_TRACE_MASK;
}
if (LOG.isTraceEnabled()) {
ZooTrace.logRequest(LOG, traceMask, 'P', request, "");
}
if (Request.requestOfDeath == request) {
break;
}
//去处理请求,处理请求中会按照request.type 进行类处理
// 而watcher 此时的请求类型是在exists 方法中指定了 exists 类型
// 最终都会执行 nextProcessor.processRequest(request); 方法
pRequest(request);
}
} catch (RequestProcessorException e) {
if (e.getCause() instanceof XidRolloverException) {
LOG.info(e.getCause().getMessage());
}
handleException(this.getName(), e);
} catch (Exception e) {
handleException(this.getName(), e);
}
LOG.info("PrepRequestProcessor exited loop!");
}
protected void pRequest(Request request) throws RequestProcessorException {
//调用 SyncRequestProcessor.processRequest()
nextProcessor.processRequest(request);
}
SyncRequestProcessor.processRequest() 方法
public void processRequest(Request request) {
// request.addRQRec(">sync");
// 也是通过调用run 方法进行异步操作
queuedRequests.add(request);
}
@Override
public void run() {
try {
while (true) {
if (si != null) {
} else if (toFlush.isEmpty()) {
// optimization for read heavy workloads
// iff this is a read, and there are no pending
// flushes (writes), then just pass this to the next
// processor
if (nextProcessor != null) {
//调用 FinalRequestProcessor.processRequest()
nextProcessor.processRequest(si);
if (nextProcessor instanceof Flushable) {
((Flushable)nextProcessor).flush();
}
}
continue;
}
}
}
} catch (Throwable t) {
}
}
调用 FinalRequestProcessor.processRequest() 方法
//调用 FinalRequestProcessor
public void processRequest(Request request) {
ServerCnxn cnxn = request.cnxn;
try {
switch (request.type) {
case OpCode.exists: {
lastOp = "EXIS";
// TODO we need to figure out the security requirement for this!
ExistsRequest existsRequest = new ExistsRequest();
ByteBufferInputStream.byteBuffer2Record(request.request,
existsRequest);
String path = existsRequest.getPath();
if (path.indexOf('\0') != -1) {
throw new KeeperException.BadArgumentsException();
}
//获得 节点信息 ,最后调用DataTree.statNode 将watch 事件进行存储
Stat stat = zks.getZKDatabase().statNode(path, existsRequest
.getWatch() ? cnxn : null);
rsp = new ExistsResponse(stat);+++
break;
}
} catch (SessionMovedException e) {
}
}