浅析ZooKeeper的Watcher机制

Watcher是实现ZooKeeper的发布/订阅功能最核心的一个角色。当我们需要对ZooKeeper的某一个节点的变化做出后续处理时,就需要使用到Watcher。

ZooKeeper的Watcher机制,总的来说可以分为三个流程:client注册Watcher,server处理Watcher,client回调Watcher。接下来我将从上面三个流程分析Watcher是如何工作的。


Client注册Watcher

Client中有一个ZKWatcher Manager用来保存所有注册的Watcher 。

1.使用默认的构造函数注册:

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher)

这个Watcher 会作为整个会话期间的默认的Watcher,保存在defaultWathcer。
2.使用getData、exist方法注册,这里以getData为例子进行分析,getData有两个重载的方法:

public byte[] getData(String path, boolean watch, Stat stat)

这个boolean 变量表示是否注册默认的Watcher

public byte[] getData(final String path, Watcher watcher, Stat stat)

public byte[] getData(final String path, Watcher watcher, Stat stat)
        throws KeeperException, InterruptedException
     {
        final String clientPath = path;
        PathUtils.validatePath(clientPath);

        // the watch contains the un-chroot path
        //WatchRegistration 用来保存Watch和path的对应关系
        WatchRegistration wcb = null;
        if (watcher != null) {
            wcb = new DataWatchRegistration(watcher, clientPath);
        }

        final String serverPath = prependChroot(clientPath);

        RequestHeader h = new RequestHeader();
        h.setType(ZooDefs.OpCode.getData);
        GetDataRequest request = new GetDataRequest();
        request.setPath(serverPath);
        //对于request请求进行标记,表示此请求需要注册watcher 
        request.setWatch(watcher != null);
        GetDataResponse response = new GetDataResponse();
        //设置ReplyHeader
        ReplyHeader r = cnxn.submitRequest(h, request, response, wcb);
        if (r.getErr() != 0) {
            throw KeeperException.create(KeeperException.Code.get(r.getErr()),
                    clientPath);
        }
        if (stat != null) {
            DataTree.copyStat(response.getStat(), stat);
        }
        return response.getData();
    }

WatchRegistration 用来保存Watch和path的对应关系的。在ZooKeeper中,最小的通信单元是Packet,所以需要把WatchRegistration用Packet进行包装。包装成Packet以后,放入队列中等待发送。

Client发送这个Packet以后,由SendThread线程的readResponse()来接受响应,然后调用finishPacket(packet)方法从packet中提出Wathcer,并且注册到ZKWatcherManager中。

private void finishPacket(Packet p) {
        if (p.watchRegistration != null) {
            //注册watcher
            p.watchRegistration.register(p.replyHeader.getErr());
        }

        。。。
    }

WatchRegistration.register方法。

//取Wathcer,并且注册到ZKWatcherManager中。
public void register(int rc) {
            if (shouldAddWatch(rc)) {
                Map<String, Set<Watcher>> watches = getWatches(rc);
                synchronized(watches) {
                    Set<Watcher> watchers = watches.get(clientPath);
                    if (watchers == null) {
                        watchers = new HashSet<Watcher>();
                        watches.put(clientPath, watchers);
                    }
                    watchers.add(watcher);
                }
            }
        }

服务端处理Watcher

客户端注册Watcher ,并不是真正的把完整的Watcher 进行注册,而仅仅是将RequestHeader和requst两个属性进行网络传输。

在服务端进行最后一步请求的处理的时,FinalRequestProcessor.processRequest()方法会判断此时的请求是否需要注册Watcher 。

case OpCode.getData: {
                ...
                //判断此时的请求是否需要注册Watcher 。
                byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat,
                        getDataRequest.getWatch() ? cnxn : null);
               ...
            }

如果需要注册,那么就把对应了Watcher 的ServerCnxn以及path存储在WatchManager中。


Watcher 触发

通过上面两步,Watcher 以及存在于client以及Server端了,那么Watcher 如何触发,以使得client回调呢?

我们进入WatcherManager的triggerWatch方法一探究竟。

public Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) {
        //封装WatchedEvent 
        WatchedEvent e = new WatchedEvent(type,
                KeeperState.SyncConnected, path);
        HashSet<Watcher> watchers;
        synchronized (this) {
            watchers = watchTable.remove(path);
            if (watchers == null || watchers.isEmpty()) {
                if (LOG.isTraceEnabled()) {
                    ZooTrace.logTraceMessage(LOG,
                            ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                            "No watchers for " + path);
                }
                return null;
            }
            //根据Path查询Watcher 
            for (Watcher w : watchers) {
                HashSet<String> paths = watch2Paths.get(w);
                if (paths != null) {
                    //删除Watcher ,也就是说Watcher 是一次性的
                    paths.remove(path);
                }
            }
        }
        for (Watcher w : watchers) {
            if (supress != null && supress.contains(w)) {
                continue;
            }
            //调用process来真正的触发Watcher 
            w.process(e);
        }
        return watchers;
    }

我们看一下process()方法做了什么。

abstract public void process(WatchedEvent event);

watcher的process是一个abstract方法。我们知道ServerCnxn是implemnt了Watcher接口的,而NIOServerCnxn又实现了ServerCnxn。所以看一下NIOServerCnxn中的process

synchronized public void process(WatchedEvent event) {
        //请求头标-1,表示这是一个通知
        ReplyHeader h = new ReplyHeader(-1, -1L, 0);
        if (LOG.isTraceEnabled()) {
            ZooTrace.logTraceMessage(LOG, ZooTrace.EVENT_DELIVERY_TRACE_MASK,
                                     "Deliver event " + event + " to 0x"
                                     + Long.toHexString(this.sessionId)
                                     + " through " + this);
        }

        // Convert WatchedEvent to a type that can be sent over the wire
        //包装成WatcherEvent ,以便于用来网络传输
        WatcherEvent e = event.getWrapper();
        //向客户端发送请求
        sendResponse(h, e, "notification");
    }

由此可知,process并不是真正执行Watcher的业务方法的,而是起到一个通知的作用。真正执行回调业务逻辑的在Client端。


Client端执行回调

Client端是通过ClientCnxn来管理客户端底层的网络通信的。而内部类SendThread是用来进行消息的接受以及发送。

SendThread.readRespose(),我截取了其中的一段代码

if (replyHdr.getXid() == -1) {
                // -1 means notification
                //-1表明这是一个通知,与上面的process对应
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Got notification sessionid:0x"
                        + Long.toHexString(sessionId));
                }
                WatcherEvent event = new WatcherEvent();
                //反序列化成WatcherEvent
                event.deserialize(bbia, "response");

                // convert from a server path to a client path
                //处理chrootPath ,相对路径变为绝对路径
                if (chrootPath != null) {
                    String serverPath = event.getPath();
                    if(serverPath.compareTo(chrootPath)==0)
                        event.setPath("/");
                    else
                        event.setPath(serverPath.substring(chrootPath.length()));
                }
                //WatcherEvent变成WatchedEvent 
                WatchedEvent we = new WatchedEvent(event);
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Got " + we + " for sessionid 0x"
                            + Long.toHexString(sessionId));
                }
                //将WatchedEvent 交给eventThread进行处理
                eventThread.queueEvent( we );
                return;
            }

由此可知,SendThread只是负责接受通知,真正的Watcher执行回调业务是在eventThread中。

EventThread.queueEvent

public void queueEvent(WatchedEvent event) {
            if (event.getType() == EventType.None
                    && sessionState == event.getState()) {
                return;
            }
            sessionState = event.getState();

            // materialize the watchers based on the event
            //找到此WatchedEvent相关所有watchers
            WatcherSetEventPair pair = new WatcherSetEventPair(
                    watcher.materialize(event.getState(), event.getType(),
                            event.getPath()),
                            event);
            // queue the pair (watch set & event) for later processing
            //放入waitingEvents队列中
            waitingEvents.add(pair);
        }

找到此WatchedEvent相关所有watchers以后,会把watchers放入waitingEvents队列中,EventThread的run()会不断从队列拿出任务执行。

EventThread.run()

@Override
        public void run() {
           try {
              isRunning = true;
              while (true) {
                 Object event = waitingEvents.take();
                 if (event == eventOfDeath) {
                    wasKilled = true;
                 } else {
                     //执行event
                    processEvent(event);
                 }
                 ......
        }

private void processEvent(Object event) {
          try {
              if (event instanceof WatcherSetEventPair) {
                  // each watcher will process the event
                  WatcherSetEventPair pair = (WatcherSetEventPair) event;
                  //找出所有的Watcher,然后一个一个执行process方法
                  //这里才是真正的执行了回调的业务逻辑
                  for (Watcher watcher : pair.watchers) {
                      try {
                          watcher.process(pair.event);
                      } catch (Throwable t) {
                          LOG.error("Error while calling watcher ", t);
                      }
                  }
                  ....

流程总结

1.客户端有一个ZKWatcherManager用来存储注册的Watcher
2.客户端发起一个带有注册Watcher的请求以后,首先会把此请求标记为需要注册Watcher,然后封装为Packet,发送给服务端
3.服务端接收到请求以后,读取到需要注册Watcher,那么就会用一个类似Watcher的ServerCnxt放入WatcherManager中去。
4.当Watcher对应的节点发生了Event事件,服务端就会发送一个通知,告诉客户端path发生了XXXEvent。
5.客户端收到通知以后(SendThread来负责接受通知),找出所有的相关的Watcher,幷包装为WatcherSetEventPair,放入waiting队列。
6.客户端的EventThread执行Run方法,从waiting取出WatcherSetEventPair,找到所有的Watcher进行真正的回调业务的执行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章