這個watch和redis中的watch是十分相似的。客戶端注冊監(jiān)聽它關(guān)心的節(jié)點目錄,目錄一旦發(fā)生變化,zk就會通知客戶端。打個比方:你在看電視劇,中途插播廣告了,你不想看廣告,就出去玩了,并且你跟你媽媽說廣告播完了就通知你。在這里,你就是客戶端,你媽媽就是watch,在那里監(jiān)控著電視里播的內(nèi)容,一旦發(fā)現(xiàn)廣告播完了,就會告訴你。所以watch就是異步 + 通知 + 觸發(fā)機(jī)制。getData()、getChildren()和exist()都可以設(shè)置watcher。
1、對watch的理解:
觸發(fā):觸發(fā)分為一次性觸發(fā)和永久觸發(fā)。一次性觸發(fā)就是zk觀察一個節(jié)點,當(dāng)發(fā)生變化就通知客戶端,然后通知完就完事了,這個節(jié)點再次發(fā)生變化它也不管了。而永久觸發(fā)就是它一直在監(jiān)控著,只要有變化就會通知。
為數(shù)據(jù)設(shè)置watch:
時序性和一致性:zk在通知客戶端的時候,可以保證不同客戶端看到變化的順序是一致的。
變化類型:變化分為三種,節(jié)點變化、數(shù)據(jù)變化或者兩者都變化。
2、數(shù)據(jù)變化之一次性觸發(fā)demo:
public String getZnode(String path) throws KeeperException, InterruptedException {
String result = null;
byte[] bytes = zooKeeper.getData(path, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
getNewData(path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(bytes);
return result;
}
private String getNewData(String path) throws KeeperException, InterruptedException {
String result = null;
byte[] bytes = zooKeeper.getData(path, false, new Stat());
result = new String(bytes);
System.out.println("監(jiān)控到值有變化,新值:" + result);
return result;
}
public static void main(String[] args) throws Exception {
Watches watches = new Watches();
ZooKeeper zooKeeper = watches.startZk();
watches.setZooKeeper(zooKeeper);
if (watches.getZooKeeper().exists(PATH,false) == null){
watches.createZnode(PATH,"hello");
String returnInfo = watches.getZnode(PATH);
System.out.println("第一次拿到的值:" + returnInfo);
System.in.read();
}
}
首先在getZnode方法里的getData方法里面的參數(shù)Watcher不是false,而是new了一個,重寫其方法,在里面再次調(diào)用獲取數(shù)據(jù)的方法。在main方法里面先將節(jié)點設(shè)置"hello",會打印出“第一次拿到的值為hello”。由于有System.in.read(),所以main線程不會結(jié)束,此時我們在Linux中啟動zkCli.sh,將節(jié)點值設(shè)置為“niubi”,控制臺就會立即打印出新的值。但是再次更改,不會再監(jiān)控。
3、數(shù)據(jù)變化之永久觸發(fā)demo:
第一次設(shè)置的值是"xixi",然后就監(jiān)控"xixi",然后改成"haha",發(fā)現(xiàn)值變了,那么就會通知,這時再監(jiān)控"heihei",如果再次修改,又會觸發(fā)通知。也就是說每次都是監(jiān)控最新值。
// 定義全局變量存儲從zk中拿到的值
private String oldValue = null;
// 獲取zk節(jié)點值的方法
public String getZnode(String path) throws KeeperException, InterruptedException {
String result = null;
byte[] bytes = zooKeeper.getData(path, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
// 獲取新值
getNewData(path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(bytes);
// 將本次獲取到的值存起來
oldValue = result;
return result;
}
// 獲取新值的方法
private boolean getNewData(String path) throws KeeperException, InterruptedException {
String result = null;
// 獲取新值的時候再new 一個 watcher,再對當(dāng)前獲取到的值進(jìn)行監(jiān)控
byte[] bytes = zooKeeper.getData(path, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
try {
// 在這里再次調(diào)用自己本身,實現(xiàn)長效監(jiān)控,相當(dāng)于遞歸
getNewData(path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}, new Stat());
result = new String(bytes);
String newValue = result;
if (oldValue.equals(newValue)){
System.out.println("監(jiān)控到值沒有變化,新舊值都為:" + result);
return false;
}else {
System.out.println("監(jiān)控到值有變化,舊值為:" + oldValue +
", 新值為:" + newValue);
// 新值變成了老值,繼續(xù)下一次的監(jiān)控
oldValue = newValue;
return true;
}
}
4、子節(jié)點變化demo:
監(jiān)控子節(jié)點變化也就說我們監(jiān)控一個父節(jié)點,當(dāng)發(fā)現(xiàn)這個父節(jié)點下面有子節(jié)點的增刪時,就會觸發(fā)通知。更多細(xì)節(jié)請看下面的代碼以及注釋。
// 1. 獲得zk實例
public ZooKeeper startZk() throws IOException {
return new ZooKeeper(CONNECTURL, SESSION_TIMEOUT, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
// 一開始if條件不會成立,因為還沒有獲取子節(jié)點,所以沒有子節(jié)點變化
if (watchedEvent.getType() == Event.EventType.NodeChildrenChanged && watchedEvent.getPath().equals(PATH)){
// 如果path父節(jié)點下的子節(jié)點有變化,就打印出這些節(jié)點
printChildNode(PATH);
}else {
// 第一次執(zhí)行會進(jìn)入這個else,會獲取path下所有的子節(jié)點
aquireParentNode(PATH);
}
}
});
}
// 獲取需要監(jiān)控的父節(jié)點下的初始子節(jié)點, path就是要監(jiān)控的父節(jié)點
private void aquireParentNode(String path) {
List<String> childNodes = null;
try {
childNodes = zooKeeper.getChildren(path, true);
System.out.println(path + " 下的初始子節(jié)點有: " + childNodes );
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
// 如果path節(jié)點下的子節(jié)點有變化,就打印出這些子節(jié)點
private void printChildNode(String path) {
List<String> childNodes = null;
try {
childNodes = zooKeeper.getChildren(path, true);
System.out.println("監(jiān)控到 " + path + " 下的子節(jié)點發(fā)生變化,變化后的子節(jié)點列表為:" + childNodes );
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
六、zookeeper集群