1. 設置Watcher
使用Watcher需要先實現Watcher接口,並將實現類對象傳遞到指定方法中,如getChildren, exist等。Zookeeper允許在構造Zookeeper對象時候指定一個默認Watcher對象.getChildren和exit方法可以使用這個默認的Watcher對象,也可以指定一個新Watcher對象。
Code 1: Watcher接口
public interface Watcher { /** * Event的狀態 */ public interface Event { /** * 在事件發生時,ZooKeeper的狀態 */ public enum KeeperState { @Deprecated Unknown (-1), Disconnected (0), @Deprecated NoSyncConnected (1), SyncConnected (3), AuthFailed (4), ConnectedReadOnly (5), SaslAuthenticated(6), Expired (-112); private final int intValue; KeeperState( int intValue) { this.intValue = intValue; } ...... } /** * ZooKeeper中的事件 */ public enum EventType { None (-1), NodeCreated (1), NodeDeleted (2), NodeDataChanged (3), NodeChildrenChanged (4); private final int intValue; // Integer representation of value // for sending over wire EventType( int intValue) { this.intValue = intValue; } ...... } } //Watcher的回調方法 abstract public void process(WatchedEvent event); }
Code 2: Zookeeper.getChildren(final String, Watcher)方法
public List<String> getChildren(final String path, Watcher watcher) throws KeeperException, InterruptedException { final String clientPath = path; PathUtils. validatePath(clientPath); WatchRegistration wcb = null; //如果watcher不等於null, 構建WatchRegistration對象, //該對象描述了watcher和path之間的關係 if (watcher != null) { wcb = new ChildWatchRegistration(watcher, clientPath); } //在傳入的path加上root path前綴,構成服務器端的絕對路徑 final String serverPath = prependChroot(clientPath); //構建RequestHeader對象 RequestHeader h = new RequestHeader(); //設置操作類型爲OpCode. getChildren h.setType(ZooDefs.OpCode. getChildren); //構建GetChildrenRequest對象 GetChildrenRequest request = new GetChildrenRequest(); //設置path request.setPath(serverPath); //設置是否使用watcher request.setWatch(watcher != null); //構建GetChildrenResponse對象 GetChildrenResponse response = new GetChildrenResponse(); //提交請求,並阻塞等待結果 ReplyHeader r = cnxn.submitRequest(h, request, response, wcb); if (r.getErr() != 0) { throw KeeperException.create(KeeperException.Code. get(r.getErr()), clientPath); } return response.getChildren(); }
Follower的NIOServerCnxn類接到了Client的請求,會調用ZookeeperServer.processPacket()方法。該方法會構建一個Request對象,並調用第一個處理器FollowerRequestProcessor。
由於我們的請求只是一個讀操作,而不是一個Quorum請求或者sync請求,所以FollowerRequestProcessor不需要調用Follower.request()方法將請求轉給Leader,只需要將請求傳遞到下一個處理器CommitProcessor。
處理器CommitProcessor線程發現請求是讀請求後,直接將Requet對象加入到toProcess隊列中,在接下的循環中會調用FinalRequestProcessor.processRequest方法進行處理。
FinalRequestProcessor.processRequest方法最終會調用ZKDatabase中的讀操作方法(如statNode和getData方法), 而ZKDatabase的這些方法會最終調用DataTree類的方法來獲取指定path的znode信息並返回給Client端,同時也會設置Watcher。
Code 3: FinalRequestProcessor對OpCode.getData請求的處理
case OpCode. getData: { lastOp = "GETD"; GetDataRequest getDataRequest = new GetDataRequest(); ByteBufferInputStream. byteBuffer2Record(request.request, getDataRequest); //獲得znode對象 DataNode n = zks.getZKDatabase().getNode(getDataRequest.getPath()); //n爲null, 拋出NoNodeException異常 if (n == null) { throw new KeeperException.NoNodeException(); } Long aclL; synchronized(n) { aclL = n. acl; } //檢查是否有讀權限 PrepRequestProcessor. checkACL(zks, zks.getZKDatabase().convertLong(aclL), ZooDefs.Perms. READ, request. authInfo); //構建狀態對象stat Stat stat = new Stat(); //獲得指定path的znode數據, //如果GetDataRequest.getWatcher()返回true, 將ServerCnxn類型對象cnxn傳遞進去。 //ServerCnxn是實現了Watcher接口 byte b[] = zks.getZKDatabase().getData(getDataRequest.getPath(), stat, getDataRequest. getWatch() ? cnxn : null); //構建GetDataResponse對象 rsp = new GetDataResponse(b, stat); break; }
Code 4: DataTree.getData()方法
public byte[] getData(String path, Stat stat, Watcher watcher) throws KeeperException.NoNodeException { //從nodes map中獲取指定path的DataNode對象 DataNode n = nodes.get(path); //如果n爲null, 則拋出NoNodeException異常 if (n == null) { throw new KeeperException.NoNodeException(); } synchronized (n) { //將n的狀態copy到stat中 n.copyStat(stat); //如果watcher不會null, 則將(path, watcher)鍵值對放入dataWatchers Map裏 if (watcher != null) { dataWatches.addWatch(path, watcher); } //返回節點數據 return n.data ; } }
2. 修改znode數據觸發Watcher
在Zookeeper二階段提交的COMMIT階段。當Follower從Leader那接收到一個寫請求的Leader.COMMIT數據包,會調用FinalRequestProcessor.processRequest()方法。Leader本身在發送完Leader.COMMIT數據包,也會調用FinalRequestProcessor.processRequest()方法。
如果是setData修改數據請求,那麼FinalRequestProcessor.processRequest()方法最終會調用到DataTree.setData方法將txn應用到指定znode上,同時觸發Watcher,併發送notification給Client端。
其關SetData請求的時序圖如下:
Code 5: DataTree.setData()方法
public Stat setData(String path, byte data[], int version, long zxid, long time) throws KeeperException.NoNodeException { Stat s = new Stat(); //根據path, 獲得DataNode對象n DataNode n = nodes.get(path); //如果n爲null, 則拋出NoNodeException異常 if (n == null) { throw new KeeperException.NoNodeException(); } byte lastdata[] = null; synchronized (n) { lastdata = n. data; n. data = data; n. stat.setMtime(time); n. stat.setMzxid(zxid); n. stat.setVersion(version); n.copyStat(s); } // now update if the path is in a quota subtree. String lastPrefix = getMaxPrefixWithQuota(path); if(lastPrefix != null) { this.updateBytes(lastPrefix, (data == null ? 0 : data.length) - (lastdata == null ? 0 : lastdata.length )); } //觸發Watcher dataWatches.triggerWatch(path, EventType.NodeDataChanged); return s; }
Code 6: WatchManage.triggerWatcher()方法,觸發Watcher。
Set<Watcher> triggerWatch(String path, EventType type, Set<Watcher> supress) { WatchedEvent e = new WatchedEvent(type, KeeperState. SyncConnected, path); HashSet<Watcher> watchers; synchronized (this ) { //從watchTable刪除掉path對於的watcher 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; } for (Watcher w : watchers) { HashSet<String> paths = watch2Paths.get(w); if (paths != null) { paths.remove(path); } } } //循環處理所有關於path的Watcher, 這裏Watcher對象實際上就是ServerCnxn類型對象 for (Watcher w : watchers) { if (supress != null && supress.contains(w)) { continue; } w.process(e); } return watchers; }
Code 7: NIOServerCnxn.process方法,發送notification給Client端
synchronized public void process (WatchedEvent event) { 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 e = event.getWrapper(); //發送notification給Client端 sendResponse(h, e, "notification"); }
3. 總結
Watcher具有one-time trigger的特性,在代碼中我們也可以看到一個watcher被處理後會立即從watchTable中刪掉。
本博客系博主原創,轉載請附上原博客地址:http://blog.csdn.net/jeff_fangji/article/details/43910113