zookeeper的分布式订阅/通知功能就是由watcher实现的。利用watcher的通知可以实现许多功能,比如分布式锁、分布式队列。
他的实现原理很简单:客户端向某一个节点注册watcher,服务端触发watcher通知到客户端,客户端调用对应watcher的回调方法。
下边分析源码:
我们先看一下哪些方法里有watcher:
1、首先,在创建zookeeper客户端的时候传入了一个watcher的实现,这个watcher将被作为该客户端的默认watcher。
构造方法列表:
客户端具体构造:
public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly) throws IOException { LOG.info("Initiating client connection, connectString=" + connectString + " sessionTimeout=" + sessionTimeout + " watcher=" + watcher); //客户端默认watcher watchManager.defaultWatcher = watcher; ConnectStringParser connectStringParser = new ConnectStringParser( connectString); HostProvider hostProvider = new StaticHostProvider( connectStringParser.getServerAddresses()); cnxn = new ClientCnxn(connectStringParser.getChrootPath(), hostProvider, sessionTimeout, this, watchManager, getClientCnxnSocket(), canBeReadOnly); cnxn.start(); }
2、getData、getChildren和exist方法都有可以传入一个watcher实现的重载方法。
注意到上图中有一些重载方法可以传入一个布尔值的watch,如果传true,将会使用构造客户端时传入的默认watcher。如下
public byte[] getData(String path, boolean watch, Stat stat) throws KeeperException, InterruptedException { //如果为true,则使用默认watcher return getData(path, watch ? watchManager.defaultWatcher : null, stat); }
了解完了哪里用到watcher,我们看看是怎么实现通知的,以getData为例:
1、客户端注册:
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 wcb = null; if (watcher != null) { //如果watcher不为空,则包装成一个WatchRegistration对象 wcb = new DataWatchRegistration(watcher, clientPath); } //增加前缀路径 final String serverPath = prependChroot(clientPath); //设置request,如果需要注册watcher则将setWatch设为true,提交请求 RequestHeader h = new RequestHeader(); h.setType(ZooDefs.OpCode.getData); GetDataRequest request = new GetDataRequest(); request.setPath(serverPath); request.setWatch(watcher != null); GetDataResponse response = new GetDataResponse(); ReplyHeader r = cnxn.submitRequest(h, request, response, web); if (r.getErr() != 0) { throw KeeperException.create(KeeperException.Code.get(r.getErr()), clientPath); } if (stat != null) { DataTree.copyStat(response.getStat(), stat); } return response.getData(); }
public ReplyHeader submitRequest(RequestHeader h, Record request, Record response, WatchRegistration watchRegistration) throws InterruptedException { ReplyHeader r = new ReplyHeader(); //将请求包装成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; synchronized (outg