1. 程式人生 > >ZooKeeper系統模型之Watcher——資料變更的通知(介面、事件、回撥方法)。

ZooKeeper系統模型之Watcher——資料變更的通知(介面、事件、回撥方法)。

        ZooKeeper提供了分散式資料的釋出/訂閱功能。一個典型的釋出/訂閱模型系統定義了一種一對多的訂閱關係,能夠讓多個訂閱者同時監聽某一個主題物件,當這個主題物件自身狀態變化時,會通知所有訂閱者,使他們能夠做出相應的處理。在ZooKeeper中,引入了Watcher機制來實現這種分散式的通知功能。ZooKeeper允許客戶端向服務端註冊一個Watcher機制來實現這種分散式的通知功能。ZooKeeper允許客戶端向服務端註冊一個Watcher監聽,當服務端的一些指定事件觸發了這個Watcher,那麼就會向指定客戶端傳送一個事件通知來實現分散式的通知功能。

        整個Watcher註冊與通知過程如下圖所示。

        從上圖中,我們可以看到,ZooKeeper的Watcher機制主要包括客戶端執行緒、客戶端WatchManager和ZooKeeper伺服器三部分。在具體工作流程上,簡單地講,客戶端在向ZooKeeper伺服器註冊Watcher的同時,會將Watcher物件儲存在客戶端的WatchManager中。當ZooKeeper伺服器端觸發Watcher事件後,會向客戶端傳送通知,客戶端執行緒從WatchManager中取出對應的Watcher物件來執行回撥邏輯。

Watcher介面

        在ZooKeeper中,介面類Watcher用於表示一個標準的事件處理器,其定義了事件通知相關的邏輯,包含KeeperState和EventType兩個列舉類,分別代表了通知狀態和事件型別,同時定義了事件的回撥方法:process(WatchedEvent event)。

Watcher事件

        同一個事件型別在不同的通知狀態中代表的含義有所不同,下表列舉了常見的通知狀態和事件型別。

KeeperState EventType 觸發條件 說明
SyncConnected(3) None(-1) 客戶端與伺服器成功建立會話 此時客戶端和伺服器處於連線狀態
NodeCreated(1) Watcher監聽的對應資料節點被建立
NodeDeleted(2) Watcher監聽的對應資料節點被刪除
NodeDataChanged(3) Watcher監聽的對應資料節點的資料內容發生變更
NodeChildrenChanged(4) Watcher監聽的對應資料節點的子節點列表發生變更
Disconnected(0) None(-1) 客戶端與ZooKeeper伺服器斷開連線 此時客戶端和伺服器處於斷開連線狀態
Expired(-112) None(-1) 會話超時 此時客戶端會話失效,通常同時也會收到SessionExpiredException異常
AuthFailed(4) None(-1) 通常有兩種情況:
  • 使用錯誤的scheme進行許可權檢查。
  • SASL許可權檢查失敗。
通常同時也會收到AuthFailedException異常
Unknown(-1) 從3.1.0版本開始廢棄
NoSyncConnected(1) 從3.1.0版本開始廢棄

         上表中列舉了ZooKeeper中最常見的幾個通知狀態和事件型別。其中,針對NodeDataChanged事件,此處說的變更包括節點的資料內容和資料的版本號dataVersion。因此即使使用相同的資料內容來更新,還是會觸發這個事件通知,因為對於ZooKeeper來說,無論資料內容是否變更,一旦有客戶端呼叫了資料更新的介面,且更新成功,就會更新dataVersion值。

        NodeChildrenChanged事件會在資料節點的子節點列表發生變更的時候被觸發,這裡說的子節點列表變化特指子節點個數和組合情況的變更,即新增子節點或刪除子節點,而子節點內容的變化是不會觸發這個事件的。

        對於AuthFailed這個事件,需要注意的地方是,他的觸發條件並不是簡簡單單因為當前客戶端會話沒有許可權,而是授權失敗。我們首先通過下面的兩個例子來看看AuthFailed事件。

              // 使用正確的Scheme進行授權

zkClient = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed1());

zkClient.addAuthInfo("digest", "taokeeper:true".getBytes());

zkClient.create("/zk-book", "".getBytes(), acls, CreateMode.EPHEMERAL );

zkClient_error = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed1());

zkClient_error.addAuthInfo("digest", "taokeeper:error".getBytes() );

zkClient_error.getData("/zk-book", true, null);

// 使用錯誤的Scheme進行授權

zkClient = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed2());

zkClient.addAuthInfo("digest", "taokeeper:true".getBytes());

zkClient.create("/zk-book", "".getBytes(), acls, CreateMode.EPHEMERAL );

zkClient_error = new ZooKeeper(SERVER_LIST, 3000, new Sample_AuthFailed2());

zkClient_error.addAuthInfo("digest2", "taokeeper:error".getBytes() );

zkClient_error.getData("/zk-book", true, null);

         上面兩個示例程式都建立了一個受到許可權控制的資料節點,然後使用了不同的許可權Scheme進行許可權檢查。在第一個示例程式中,使用了正確的許可權Scheme:digest;而第二個示例程式中使用了錯誤的Scheme:digest2。另外,無論哪個程式,都使用了錯誤的Auth:taokeeper:error,因此在執行第一個程式的時候,會丟擲NoAuthException異常,而第二個程式執行後,丟擲的是AuthFailedException異常,同時,會收到對應的Watcher事件通知:(AuthFailed, None)。

回撥方法process()

        process方法是Watcher介面中的一個回撥方法,當ZooKeeper向客戶端傳送一個Watcher事件通知時,客戶單就會對相應的process方法進行回撥,從而實現對事件的處理。process方法的定義如下:

  • abstract public void process(WatchedEvent event);

        這個回掉方法的定義非常簡單,我們重點看下方法的引數定義:WatchedEvent。

        WatchedEvent包含了每一個事件的三個基本方法:通知狀態(keeperState)、事件型別(eventType)和節點路徑(path),其資料結構如下圖所示。ZooKeeper使用WatchedEvent物件來封裝服務端事件並傳遞給Watcher,從而方便回撥方法process對服務端事件進行處理。

        提到WatchedEvent,不得不講下WatcherEvent實體。籠統的講,兩者表示的是同一個事務,都是對一個服務端事件的封裝。不同的是,WatchedEvent是一個邏輯事件,用於服務端和客戶端程式執行過程中所需的邏輯物件,而WatcherEvent因為實現了序列化介面,因此可以用於網路傳輸,其資料結構如下圖所示。

        服務端在生成WatchedEvent事件之後,會呼叫getWrapper方法將自己包裝成一個可序列化的WatcherEvent事件,以便通過網路傳輸到客戶端。客戶端在接收到服務端的這個事件物件後,首先會將WatcherEvent事件還原成一個WatchedEvent事件,並傳遞給process方法處理,回撥方法process根據入參就能夠解析出完整的服務端事件了。

        需要注意的一點是,無論是WatchedEvent還是WatcherEvent,其對ZooKeeper服務端事件的封裝都是及其簡單的。舉個例子來說,當/zk-book這個節點的資料發生變更時,服務端會發送給客戶端一個“ZNode資料內容變更”事件,客戶端只能收到如下資訊:

        從上面展示的資訊中,我們可以看到,客戶端無法直接從該事件中獲取到對應資料節點的原始資料內容以及變更後的新資料內容,而是需要客戶端再次主動去重新獲取資料——這也是ZooKeeper Watcher機制的一個非常重要的特性。