ZooKeeper客戶端原始碼分析
1、執行緒模型
ZK客戶端啟動時會啟動兩個執行緒,一個叫SendThread,另一個叫EventThread。SendThread和伺服器打交道負責發包收包。EventThread負責處理事件,比如執行非同步請求的回撥函式,處理伺服器主動推送的通知包(在伺服器端註冊了watcher就能收到這種通知包)。
ZooKeeper客戶端對於使用者來說,大部分方法都是執行緒安全的,也就是說多個執行緒能同時掉用一個zookeeper客戶端例項。在傳送請求時,執行緒會阻塞在它的請求包上,直到收到響應包執行緒才會被喚醒。
非同步請求的回撥函式是在EventThread中呼叫的,Watcher的process函式也是在這個執行緒中掉用,所以在開發回撥函式和process函式時要注意不要阻塞這條執行緒。
2、xid和zxid
xid的作用是保證伺服器的響應包順序和客戶的請求包的順序一致,客戶端在傳送請求時會遞增這個值並賦予請求包;每次收到響應包時都會檢查請求佇列頭部的請求包的xid是否和響應包的xid一致,如果不一致的話那麼就斷開連線。當xid為負值時,比如-2、-4、-1時,他們分別表示當前包為ping響應包、認證響應包、通知包,這些包不需要匹配請求佇列裡請求包,換句話說那就是它們對應的請求包並沒有加入請求佇列中。
zxid由伺服器提供,表示伺服器端狀態的版本。客戶端收到響應包後會從包中取出此值並更新本地的lastZxid變數。當客戶端遇到網路故障並重新尋找一個可用的伺服器時,它會詢問伺服器的zxid是多少,如果這個zxid小於lastZxid,客戶端會跳過此伺服器嘗試和下一個建立連線。
4、ClientCnxn
這個類為客戶端管理socket i/o。ClientCnxn管理一個可用伺服器列表並且在需要時“透明地”切換伺服器。
4.1、pendingQueue和outgoingQueue
ClientCnxn擁有這兩條佇列,pendingQueue中的包均已經發送給伺服器,正在等待響應;outgoingQueue的包還沒有傳送給伺服器,還在等待被髮送。
5、事件執行緒(EventThread)
5.1、事件執行緒可以被關掉
會話過期後,事件執行緒就被要求關閉。事件執行緒一旦關閉就不能再啟動,如果還想繼續使用ZK服務,應用需要再次建立一個新的ZooKeeper物件,以此來啟動事件執行緒。
6、傳送執行緒(SendThread)
7、Watcher
7.1、Watcher的原理
Watcher分為客戶端Watcher和伺服器端Watcher。客戶端watcher需要實現介面org.apache.zookeeper.Watcher,並將實現的物件傳給在ZooKeeper的一些方法中,比如:
public List<String> getChildren(final String path, Watcher watcher, Stat stat) {}
上面的程式碼如果被執行,客戶端向伺服器端傳送請求裡有個叫watch的欄位會被設定成true,這樣服務端就會分配一個Watcher(服務端Watcher)來偵聽“DataTree”中某個節點的事件。而客戶端也會將傳入的watcher物件用Watcher管理器管理(ZKWatchManager)起來。
當偵聽的事件發生時,比如說子節點變少了,伺服器端Watcher會向客戶端傳送通知,告訴它子節點列表有更新,同時也會刪除服務端的Watcher。
客戶端收到通知後,會將本地的Watcher管理器的和path關聯watcher取出同時移除出管理器,然後並觸發它們工作。下面這個方法就是完成這個任務的核心方法:
public Set<Watcher> materialize(Watcher.Event.KeeperState state, Watcher.Event.EventType type, String clientPath)
綜上可以看出,不管是客戶端還是服務端,Watcher的生命週期都是很短暫的(除了預設Wather),事件一旦發生,它們在兩端都會被刪除,如果想繼續監聽節點的事件,客戶端必須再次呼叫上面說到的getChildren,再次再兩端分配Watcher才能偵聽到接下來的事件。