在使用zkClient監聽機制時,發現當代碼中連續對節點的數據進行變更時,若變更之間的時間間隔比較短,則會出現事件監聽丟失的情況或者監聽到的事件重複的情況,本人代碼如下:
package com.ant.zookeeperstudy;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import org.apache.zookeeper.CreateMode;
import org.junit.Test;
import java.util.concurrent.TimeUnit;
public class ZkClientTest {
// private static String connect = "192.168.109.130:2181,192.168.109.131:2181,192.168.109.132:2181";
private static String connect = "192.168.162.139:2181,192.168.162.140:2181,192.168.162.141:2181,192.168.162.142:2181,192.168.162.143:2181,192.168.162.144:2181,192.168.162.145:2181";
@Test
public void test1() throws InterruptedException {
ZkClient client = new ZkClient(connect);
String ant = client.create("/ant", "ant", CreateMode.EPHEMERAL);
client.subscribeDataChanges(ant, new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
System.out.println("dataChange:" + dataPath + " " + data);
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
System.out.println("數據被刪除");
}
});
TimeUnit.HOURS.sleep(1);
}
@Test
public void test2()throws Exception{
ZkClient client = new ZkClient(connect);
client.writeData("/ant","one");
// System.out.println("saven");
// TimeUnit.SECONDS.sleep(2);
client.writeData("/ant","two");
// System.out.println("four");
// TimeUnit.SECONDS.sleep(2);
client.writeData("/ant","three");
// System.out.println("three");
// TimeUnit.SECONDS.sleep(2);
client.delete("/ant");
TimeUnit.SECONDS.sleep(2);
}
}
多次執行監聽到的結果如下:
當將代碼中數據修改代碼中增加睡眠則不會出現重複監聽或漏的問題,原因如下文:
zookeeper丟失event問題(源於https://www.iteye.com/blog/leibnitz-1880577)
所 有的Zookeeper讀操作,包括getData()、getChildren()和exists(),都有一個開關,可以在操作的同時再設置一個 watch。在ZooKeeper中,Watch是一個一次性觸發器,會在被設置watch的數據發生變化的時候,發送給設置watch的客戶端。 watch的定義中有三個關鍵點:
-
一次性觸發器
一 個watch事件將會在數據發生變更時發送給客戶端。例如,如果客戶端執行操作getData(“/znode1″, true),而後 /znode1 發生變更或是刪除了,客戶端都會得到一個 /znode1 的watch事件。如果 /znode1 再次發生變更,則在客戶端沒有設置新的watch的情況下,是不會再給這個客戶端發送watch事件的。
-
發送給客戶端
這 就是說,一個事件會發送向客戶端,但可能在在操作成功的返回值到達發起變動的客戶端之前,這個事件還沒有送達watch的客戶端。Watch是異步發送 的。但ZooKeeper保證了一個順序:一個客戶端在收到watch事件之前,一定不會看到它設置過watch的值的變動。網絡時延和其他因素可能會導 致不同的客戶端看到watch和更新返回值的時間不同。但關鍵點是,每個客戶端所看到的每件事都是有順序的。
-
被設置了watch的數據
這 是指節點發生變動的不同方式。你可以認爲ZooKeeper維護了兩個watch列表:data watch和child watch。getData()和exists()設置data watch,而getChildren()設置child watch。或者,可以認爲watch是根據返回值設置的。getData()和exists()返回節點本身的信息,而getChildren()返回 子節點的列表。因此,setData()會觸發znode上設置的data watch(如果set成功的話)。一個成功的 create() 操作會觸發被創建的znode上的數據watch,以及其父節點上的child watch。而一個成功的 delete()操作將會同時觸發一個znode的data watch和child watch(因爲這樣就沒有子節點了),同時也會觸發其父節點的child watch。
Watch 由client連接上的ZooKeeper服務器在本地維護。這樣可以減小設置、維護和分發watch的開銷。當一個客戶端連接到一個新的服務器上 時,watch將會被以任意會話事件觸發。當與一個服務器失去連接的時候,是無法接收到watch的。而當client重新連接時,如果需要的話,所有先 前註冊過的watch,都會被重新註冊。通常這是完全透明的。只有在一個特殊情況下,watch可能會丟失:對於一個未創建的znode的exist watch,如果在客戶端斷開連接期間被創建了,並且隨後在客戶端連接上之前又刪除了,這種情況下,這個watch事件可能會被丟失。
ZooKeeper對Watch提供了什麼保障
對於watch,ZooKeeper提供了這些保障:
-
Watch與其他事件、其他watch以及異步回覆都是有序的。 ZooKeeper客戶端庫保證所有事件都會按順序分發。
-
客戶端會保障它在看到相應的znode的新數據之前接收到watch事件。//這保證了在process()再次利用zk client訪問時數據是存在的
-
從ZooKeeper接收到的watch事件順序一定和ZooKeeper服務所看到的事件順序是一致的。
關於Watch的一些值得注意的事情
-
Watch是一次性觸發器,如果你得到了一個watch事件,而你希望在以後發生變更時繼續得到通知,你應該再設置一個watch。
-
因 爲watch是一次性觸發器,而獲得事件再發送一個新的設置watch的請求這一過程會有延時,所以你無法確保你看到了所有發生在ZooKeeper上的 一個節點上的事件。所以請處理好在這個時間窗口中可能會發生多次znode變更的這種情況。(你可以不處理,但至少請認識到這一點)。//也就是說,在process()中如果處理得慢而沒有註冊new watch時,在這期間有其它事件出現時是不會通知!!之前可能就是沒有意識到這點所以才引出本話題***********
-
一個watch對象或一個函數/上下文對,爲一個事件只會被通知一次。比如,如果同一個watch對象在同一個文件上分別通過exists和getData註冊了兩次,而這個文件之後被刪除了,這時這個watch對象將只會收到一次該文件的deletion通知。//同一個watch註冊同一個節點多次只會生成一個event.這裏我想到如果一個watch註冊不同的node,也應當出現多個event?
-
當你從一個服務器上斷開時(比如服務器出故障了),在再次連接上之前,你將無法獲得任何watch。請使用這些會話事件來進入安全模式:在disconnected狀態下你將不會收到事件,所以你的程序在此期間應該謹慎行事。