github: https://github.com/zhaikaishun/zookeeper_tutorial
Zookeeper的watcher事件
zookeeper有watch事件,是一次性觸發的,當watch監視的數據發生變化時,通知設置了該watch的client,即watcher。
同樣,其watcher是監聽數據發生了某些變化,那就一定會有對應的事件類型,和狀態類型。
事件類型(znode節點相關的)
- EventType.NodeCreated
- EventType.NodeDataChanged
- EventType.NodeChildrenChanged
- EventType.NodeDeleted
狀態類型:(是跟客戶端實例相關的,簡單的說就是客戶端和服務器端連接狀態相關的)
- KeeperState.Disconnected
- KeeperState.SyncConnected
- KeeperState.AuthFailed 認證失敗
- KeeperState.Expired 過期
watcher和watch
簡單的說,一個節點上的某個程序監控某個節點,那麼這個節點上的這個程序就是一個watcher,而監聽的這個事件(動作),就是一個watch。watch事件,是一次性觸發的,只能監聽一次,第二次對此節點的修改就監聽不到了,如果想一直監聽,大概有兩種方案,一種是在出發事件後執行方法的時候有個watch的參數再設置爲true,一種是這個時候再創建一個watch,這種是有點麻煩。
看一個例子吧:
最好是去github上下載下來自己運行一下。
- 設置watcher, 我這裏每次create的時候,都設置了一下watcher,例如下面代碼中 this.zk.exists(path, ifsetTrue);
- 需要實現implements Watcher接口以及重寫實現方法process,我這裏監聽比較簡單。也就不多說了。
- 反正是要注意,如果只設置一次監聽,那麼監聽完之後,第二次就監聽不到了。若需要多次監聽,那麼最好是再監聽一次
/**
* Zookeeper Wathcher
* 本類就是一個Watcher類(實現了org.apache.zookeeper.Watcher類)
* @author(alienware)
* @since 2015-6-14
*/
public class ZooKeeperWatcher implements Watcher {
/** 定義原子變量 */
AtomicInteger seq = new AtomicInteger();
/** 定義session失效時間 */
private static final int SESSION_TIMEOUT = 10000;
/** zookeeper服務器地址 */
private static final String CONNECTION_ADDR = "192.168.1.31:2181";
/** zk父路徑設置 */
private static final String PARENT_PATH = "/testWatch";
/** zk子路徑設置 */
private static final String CHILDREN_PATH = "/testWatch/children";
/** 進入標識 */
private static final String LOG_PREFIX_OF_MAIN = "【Main】";
/** zk變量 */
private ZooKeeper zk = null;
/** 信號量設置,用於等待zookeeper連接建立之後 通知阻塞程序繼續向下執行 */
private CountDownLatch connectedSemaphore = new CountDownLatch(1);
/**
* 創建ZK連接
* @param connectAddr ZK服務器地址列表
* @param sessionTimeout Session超時時間
*/
public void createConnection(String connectAddr, int sessionTimeout) {
this.releaseConnection();
try {
zk = new ZooKeeper(connectAddr, sessionTimeout, this);
System.out.println(LOG_PREFIX_OF_MAIN + "開始連接ZK服務器");
connectedSemaphore.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 關閉ZK連接
*/
public void releaseConnection() {
if (this.zk != null) {
try {
this.zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 創建節點
* @param path 節點路徑
* @param data 數據內容
* @return
*/
public boolean createPath(String path, String data,boolean ifsetTrue) {
try {
//設置監控(由於zookeeper的監控都是一次性的所以 每次必須設置監控)
this.zk.exists(path, ifsetTrue);
System.out.println(LOG_PREFIX_OF_MAIN + "節點創建成功, Path: " +
this.zk.create( /**路徑*/
path,
/**數據*/
data.getBytes(),
/**所有可見*/
Ids.OPEN_ACL_UNSAFE,
/**永久存儲*/
CreateMode.PERSISTENT ) +
", content: " + data);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 讀取指定節點數據內容
* @param path 節點路徑
* @return
*/
public String readData(String path, boolean needWatch) {
try {
return new String(this.zk.getData(path, needWatch, null));
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
/**
* 更新指定節點數據內容
* @param path 節點路徑
* @param data 數據內容
* @return
*/
public boolean writeData(String path, String data) {
try {
System.out.println(LOG_PREFIX_OF_MAIN + "更新數據成功,path:" + path + ", stat: " +
this.zk.setData(path, data.getBytes(), -1));
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 刪除指定節點
*
* @param path
* 節點path
*/
public void deleteNode(String path) {
try {
this.zk.delete(path, -1);
System.out.println(LOG_PREFIX_OF_MAIN + "刪除節點成功,path:" + path);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判斷指定節點是否存在
* @param path 節點路徑
*/
public Stat exists(String path, boolean needWatch) {
try {
return this.zk.exists(path, needWatch);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取子節點
* @param path 節點路徑
*/
private List<String> getChildren(String path, boolean needWatch) {
try {
return this.zk.getChildren(path, needWatch);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 刪除所有節點
*/
public void deleteAllTestPath() {
if(this.exists(CHILDREN_PATH, false) != null){
this.deleteNode(CHILDREN_PATH);
}
if(this.exists(PARENT_PATH, false) != null){
this.deleteNode(PARENT_PATH);
}
}
/**
* 收到來自Server的Watcher通知後的處理。
*/
@Override
public void process(WatchedEvent event) {
System.out.println("進入 process 。。。。。event = " + event);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (event == null) {
return;
}
// 連接狀態
KeeperState keeperState = event.getState();
// 事件類型
EventType eventType = event.getType();
// 受影響的path
String path = event.getPath();
String logPrefix = "【Watcher-" + this.seq.incrementAndGet() + "】";
System.out.println(logPrefix + "收到Watcher通知");
System.out.println(logPrefix + "連接狀態:\t" + keeperState.toString());
System.out.println(logPrefix + "事件類型:\t" + eventType.toString());
if (KeeperState.SyncConnected == keeperState) {
// 成功連接上ZK服務器
if (EventType.None == eventType) {
System.out.println(logPrefix + "成功連接上ZK服務器");
connectedSemaphore.countDown();
}
//創建節點
else if (EventType.NodeCreated == eventType) {
System.out.println(logPrefix + "節點創建");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.exists(path, true);
}
//更新節點
else if (EventType.NodeDataChanged == eventType) {
System.out.println(logPrefix + "節點數據更新");
System.out.println("我看看走不走這裏........");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(logPrefix + "數據內容: " + this.readData(PARENT_PATH, true));
}
//更新子節點
else if (EventType.NodeChildrenChanged == eventType) {
System.out.println(logPrefix + "子節點變更");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(logPrefix + "子節點列表:" + this.getChildren(PARENT_PATH, true));
}
//刪除節點
else if (EventType.NodeDeleted == eventType) {
System.out.println(logPrefix + "節點 " + path + " 被刪除");
}
else ;
}
else if (KeeperState.Disconnected == keeperState) {
System.out.println(logPrefix + "與ZK服務器斷開連接");
}
else if (KeeperState.AuthFailed == keeperState) {
System.out.println(logPrefix + "權限檢查失敗");
}
else if (KeeperState.Expired == keeperState) {
System.out.println(logPrefix + "會話失效");
}
else ;
System.out.println("--------------------------------------------");
}
/**
* <B>方法名稱:</B>測試zookeeper監控<BR>
* <B>概要說明:</B>主要測試watch功能<BR>
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//建立watcher
ZooKeeperWatcher zkWatch = new ZooKeeperWatcher();
//創建連接
zkWatch.createConnection(CONNECTION_ADDR, SESSION_TIMEOUT);
//System.out.println(zkWatch.zk.toString());
Thread.sleep(1000);
// 清理節點
zkWatch.deleteAllTestPath();
if (zkWatch.createPath(PARENT_PATH, System.currentTimeMillis() + "",true)) {
Thread.sleep(1000);
// 讀取數據
System.out.println("---------------------- read parent ----------------------------");
//zkWatch.readData(PARENT_PATH, true);
// 讀取子節點
System.out.println("---------------------- read children path ----------------------------");
zkWatch.getChildren(PARENT_PATH, true);
// 更新數據
zkWatch.writeData(PARENT_PATH, System.currentTimeMillis() + "");
Thread.sleep(1000);
// 創建子節點
zkWatch.createPath(CHILDREN_PATH, System.currentTimeMillis() + "",true);
Thread.sleep(1000);
zkWatch.writeData(CHILDREN_PATH, System.currentTimeMillis() + "");
}
Thread.sleep(50000);
// 清理節點
zkWatch.deleteAllTestPath();
Thread.sleep(1000);
zkWatch.releaseConnection();
}
}
輸出就在這裏了,想具體瞭解的話,自己敲一下,然後覆蓋一下代碼。一個一個功能的執行,查看功能即可
【Main】開始連接ZK服務器
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:None path:null
【Watcher-1】收到Watcher通知
【Watcher-1】連接狀態: SyncConnected
【Watcher-1】事件類型: None
【Watcher-1】成功連接上ZK服務器
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/testWatch
【Main】節點創建成功, Path: /testWatch, content: 1508077399368
【Watcher-2】收到Watcher通知
【Watcher-2】連接狀態: SyncConnected
【Watcher-2】事件類型: NodeCreated
【Watcher-2】節點創建
--------------------------------------------
---------------------- read parent ----------------------------
---------------------- read children path ----------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/testWatch
【Main】更新數據成功,path:/testWatch, stat: 30064771078,30064771079,1508071193606,1508071194762,1,0,0,0,13,0,30064771078
【Watcher-3】收到Watcher通知
【Watcher-3】連接狀態: SyncConnected
【Watcher-3】事件類型: NodeDataChanged
【Watcher-3】節點數據更新
我看看走不走這裏........
【Watcher-3】數據內容: 1508077400538
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeCreated path:/testWatch/children
【Main】節點創建成功, Path: /testWatch/children, content: 1508077401581
【Watcher-4】收到Watcher通知
【Watcher-4】連接狀態: SyncConnected
【Watcher-4】事件類型: NodeCreated
【Watcher-4】節點創建
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/testWatch
【Watcher-5】收到Watcher通知
【Watcher-5】連接狀態: SyncConnected
【Watcher-5】事件類型: NodeChildrenChanged
【Watcher-5】子節點變更
【Main】更新數據成功,path:/testWatch/children, stat: 30064771080,30064771081,1508071195777,1508071196784,1,0,0,0,13,0,30064771080
【Watcher-5】子節點列表:[children]
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeDataChanged path:/testWatch/children
【Watcher-6】收到Watcher通知
【Watcher-6】連接狀態: SyncConnected
【Watcher-6】事件類型: NodeDataChanged
【Watcher-6】節點數據更新
我看看走不走這裏........
【Watcher-6】數據內容: 1508077400538
--------------------------------------------
進入 process 。。。。。event = WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/testWatch
【Main】刪除節點成功,path:/testWatch/children
【Main】刪除節點成功,path:/testWatch
【Watcher-7】收到Watcher通知
【Watcher-7】連接狀態: SyncConnected
【Watcher-7】事件類型: NodeChildrenChanged
【Watcher-7】子節點變更
實際應用一個場景
我們希望zookeeper對分佈式系統的配置文件進行管理,也就是說多個服務器進行watcher,zookeeper節點發送變化,則我們實時更新配置文件。我們要完成多個應用服務器註冊watcher,然後實時觀察數據的變化,然後反饋給媒體服務器變更的數據信息,觀察zookeeper節點
下面是一個例子: 代碼在bjsxt.zookeeper.cluster中
本例子模擬多臺服務器同時監控一個節點。然後另外一個程序進行管理,所監控的這幾臺機器得到節點變更的通知。
本例中,Client1和Client2相當於兩臺服務器,共同watch一個節點。Test相當於管理者,用來管理這兩個客戶端的配置。
代碼如下,具體的需要自己下載下來進行調試
Client1
public class Client1 {
public static void main(String[] args) throws Exception{
ZKWatcher myWatcher = new ZKWatcher();
Thread.sleep(100000000);
}
}
Client2
public class Client2 {
public static void main(String[] args) throws Exception{
ZKWatcher myWatcher = new ZKWatcher();
Thread.sleep(100000000);
}
}
ZKWatcher
public class ZKWatcher implements Watcher {
/** zk變量 */
private ZooKeeper zk = null;
/** 父節點path */
static final String PARENT_PATH = "/super";
/** 信號量設置,用於等待zookeeper連接建立之後 通知阻塞程序繼續向下執行 */
private CountDownLatch connectedSemaphore = new CountDownLatch(1);
private List<String> cowaList = new CopyOnWriteArrayList<String>();
/** zookeeper服務器地址 */
public static final String CONNECTION_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
/** 定義session失效時間 */
public static final int SESSION_TIMEOUT = 30000;
public ZKWatcher() throws Exception{
zk = new ZooKeeper(CONNECTION_ADDR, SESSION_TIMEOUT, this);
System.out.println("開始連接ZK服務器");
connectedSemaphore.await();
}
@Override
public void process(WatchedEvent event) {
// 連接狀態
KeeperState keeperState = event.getState();
// 事件類型
EventType eventType = event.getType();
// 受影響的path
String path = event.getPath();
System.out.println("受影響的path : " + path);
if (KeeperState.SyncConnected == keeperState) {
// 成功連接上ZK服務器
if (EventType.None == eventType) {
System.out.println("成功連接上ZK服務器");
connectedSemaphore.countDown();
try {
if(this.zk.exists(PARENT_PATH, false) == null){
this.zk.create(PARENT_PATH, "root".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
List<String> paths = this.zk.getChildren(PARENT_PATH, true);
for (String p : paths) {
System.out.println(p);
this.zk.exists(PARENT_PATH + "/" + p, true);
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
//創建節點
else if (EventType.NodeCreated == eventType) {
System.out.println("節點創建");
try {
this.zk.exists(path, true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
//更新節點
else if (EventType.NodeDataChanged == eventType) {
System.out.println("節點數據更新");
try {
//update nodes call function
this.zk.exists(path, true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
//更新子節點
else if (EventType.NodeChildrenChanged == eventType) {
System.out.println("子節點 ... 變更");
try {
List<String> paths = this.zk.getChildren(path, true);
if(paths.size() >= cowaList.size()){
paths.removeAll(cowaList);
for(String p : paths){
this.zk.exists(path + "/" + p, true);
//this.zk.getChildren(path + "/" + p, true);
System.out.println("這個是新增的子節點 : " + path + "/" + p);
//add new nodes call function
}
cowaList.addAll(paths);
} else {
cowaList = paths;
}
System.out.println("cowaList: " + cowaList.toString());
System.out.println("paths: " + paths.toString());
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
//刪除節點
else if (EventType.NodeDeleted == eventType) {
System.out.println("節點 " + path + " 被刪除");
try {
//delete nodes call function
this.zk.exists(path, true);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
else ;
}
else if (KeeperState.Disconnected == keeperState) {
System.out.println("與ZK服務器斷開連接");
}
else if (KeeperState.AuthFailed == keeperState) {
System.out.println("權限檢查失敗");
}
else if (KeeperState.Expired == keeperState) {
System.out.println("會話失效");
}
else ;
System.out.println("--------------------------------------------");
}
}
Test
public class Test {
/** zookeeper地址 */
static final String CONNECT_ADDR = "192.168.1.31:2181,192.168.1.32:2181,192.168.1.33:2181";
/** session超時時間 */
static final int SESSION_OUTTIME = 2000;//ms
/** 信號量,阻塞程序執行,用於等待zookeeper連接成功,發送成功信號 */
static final CountDownLatch connectedSemaphore = new CountDownLatch(1);
public static void main(String[] args) throws Exception{
ZooKeeper zk = new ZooKeeper(CONNECT_ADDR, SESSION_OUTTIME, new Watcher(){
@Override
public void process(WatchedEvent event) {
//獲取事件的狀態
KeeperState keeperState = event.getState();
EventType eventType = event.getType();
//如果是建立連接
if(KeeperState.SyncConnected == keeperState){
if(EventType.None == eventType){
//如果建立連接成功,則發送信號量,讓後續阻塞程序向下執行
connectedSemaphore.countDown();
System.out.println("zk 建立連接");
}
}
}
});
//進行阻塞
connectedSemaphore.await();
// //創建子節點
// zk.create("/super/c1", "c1".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//創建子節點
// zk.create("/super/c2", "c2".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//創建子節點
String result = zk.create("/super/c3", "c3".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println(result);
//創建子節點
// zk.create("/super/c4", "c4".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// zk.create("/super/c4/c44", "c44".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
//獲取節點信息
// byte[] data = zk.getData("/testRoot", false, null);
// System.out.println(new String(data));
// System.out.println(zk.getChildren("/testRoot", false));
//修改節點的值
// zk.setData("/super/c1", "modify c1".getBytes(), -1);
// zk.setData("/super/c2", "modify c2".getBytes(), -1);
// byte[] data = zk.getData("/super/c2", false, null);
// System.out.println(new String(data));
// //判斷節點是否存在
// System.out.println(zk.exists("/super/c3", false));
// //刪除節點
// zk.delete("/super/c3", -1);
zk.close();
}
}
先啓動Client1,client1打印
開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器
再啓動Client2,
開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器
--------------------------------------------
再啓動Test類,此時Client1和Client2打印的內容是
開始連接ZK服務器
受影響的path : null
成功連接上ZK服務器
--------------------------------------------
受影響的path : /super
子節點 ... 變更
這個是新增的子節點 : /super/c3
cowaList: [c3]
paths: [c3]
Test打印的內容是
zk 建立連接
/super/c3
更多測試,需要自己來運行並且思考結果。這裏只起拋磚引玉的作用