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進行真正的回調業務的執行。