Zookeeper有watch事件,是一次性触发的,当watch监视的数据节点发生变化时,通知设置了该watch的客户端,即watcher
当然了,watcher监听数据发生了哪些变化,必须得有对应的事件类型了,在实现了Watcher接口重写的process方法里面,我们也是根据事件类型做我们的业务逻辑。
事件类型:(znode节点有关)
EventType.NodeCreated:创建新节点
EventType.NodeDataChanged:节点数据发生改变
EventType.NodeChildrenChanged:节点的子节点发生改变(新增或删除)
EventType.NodeDeleted:删除节点
状态类型:(和客户端连接zk服务器相关)
KeeperState.Disconnected:与zk服务器断开连接
KeeperState.SyncConnected:与zk服务器连接成功
KeeperState.AythFailed:检查权限失败(ACL)
KeeperState.Expired:会话失效
watcher有以下三个特性:
1、一次性:watch事件是一次性触发的,如果想再次监控数据,必须重新设置监控
2、客户端串行执行:客户端Watcher回调过程是一个串行同步的过程,这为我们保证了顺序。
3、轻量的:WatchedEvent是zk整个Watcher通知机制的最小通知单元,只有三部分组成:通知状态、时间类型、节点路径。也就是说,具体发生了什么变化,是需要客户端自己去查询的。
我自己觉得需要注意的点:
1、watch事件是一次性触发的,只要触发了一次就没了。
2、如果Zookeeper新建实例时加上了Watcher,调用方法里面带上true/false参数来表示是否添加watch事件,不然只能用匿名类来添加watch事件。
3、可以用exists方法:exists(path,true)来添加watch事件,当下次操作path会出发watch事件
例如:
zk.exists(path,true);//给path添加watch事件
zk.writeData(path,"newValue");//触发watch事件 EventType->nodeDataChenage
zk.exists(path,true);//给path添加watch事件
zk.create(path,"newValue",Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);//触发watch事件 EventType->nodeCreated
zk.exists(path,true);//给path添加watch事件
zk.delete(path,-1);//触发watch事件 EventType->nodeDeleted
4、getChildren(path,neeWatch) //给path添加watch事件,监控子节点变化
如果path新增或者删除节点会触发watch事件 EventType->nodechildrenChanged
Demo代码:
package com.demo.zookeeper.watcher;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Stat;
public class ZookeeperWatcher implements Watcher{
/**定义原子变量,来看出发了多少次watch事件*/
AtomicInteger seq = new AtomicInteger();
/** 定义session失效时间 */
private static final int SESSION_TIMEOUT = 10000;
/** zookeeper服务器地址 */
private static final String CONNECTION_ADDR = "192.168.1.111:2181,192.168.1.112:2181,192.168.1.113:2181";
/** zk父路径设置 */
private static final String PARENT_PATH = "/testWatch";
/** zk子路径设置 */
private static final String CHILDREN_PATH = "/testWatch/children";
/**zk*/
private ZooKeeper zk = null;
/** 信号量设置,用于等待zookeeper连接建立之后 通知阻塞程序继续向下执行 */
private CountDownLatch semaphore = new CountDownLatch(1);
/**
* 创建zk连接
* @param connectAddr zk服务器连接地址列表
* @param sessionTimeout Session超时时间
*/
public void createConnection(String connectAddr,int sessionTimeout){
//创建前先释放一下,防止没释放
this.releaseConnection();
try {
//wathcer为自己
this.zk = new ZooKeeper(connectAddr,sessionTimeout,this);
System.out.println("【Main】:"+"开始连接zk服务器");
semaphore.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 watch 是否添加watch事件
* @return
*/
public String readData(String path,boolean watch){
try {
return new String(this.zk.getData(path, watch, null));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 修改节点数据
* @param path
* @param data
*/
public void updateData(String path,String data){
try {
this.zk.setData(path, data.getBytes(), -1);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
/**
* 创建新节点
* @param path
* @param data
*/
public boolean createNode(String path,String data){
try {
//利用exists方法添加watch事件
this.zk.exists(path, true);
//因为上面添加了watch事件,所以接下来的创建节点一定会触发watch事件
this.zk.create(path,
data.getBytes(),
Ids.OPEN_ACL_UNSAFE, //打开ACL,不进行权限控制
CreateMode.PERSISTENT);//永久节点
System.out.println("【Main】:节点创建成功,Path:"+path);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除节点
* @param path
* @return
*/
public boolean deleteNode(String path){
try {
this.zk.delete(path,-1);
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 判断指定节点是否存在
* @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
* @param watch
* @return
*/
public List<String> getChildren(String path,boolean watch){
List<String> pathList = new ArrayList<String>();
try {
pathList = this.zk.getChildren(path, watch);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
return null;
}
return pathList;
}
/**
* 删除所有节点,因为创建的节点是限制的,所以这么直接删除
*/
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);
}
}
@Override
public void process(WatchedEvent watchedEvent) {
//获取连接事件
KeeperState keeperState = watchedEvent.getState();
//获取节点事件
EventType eventType = watchedEvent.getType();
// 受影响的path
String path = watchedEvent.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.equals(keeperState)){//成功连接
if(EventType.None.equals(eventType)){
System.out.println(logPrefix + "成功连接上ZK服务器");
semaphore.countDown();
}else if(EventType.NodeCreated.equals(eventType)){
System.out.println(logPrefix + "创建节点:" + path);
}else if(EventType.NodeDataChanged.equals(eventType)){
System.out.println(logPrefix + "修改节点"+path+"数据,修改为:"+this.readData(path,false));
}else if(EventType.NodeDeleted.equals(eventType)){
System.out.println(logPrefix + "删除节点:"+path);
}else if(EventType.NodeChildrenChanged.equals(eventType)){
System.out.println(logPrefix + path +"的子节点变更");
}
}else if(KeeperState.Disconnected.equals(keeperState)){
System.out.println(logPrefix + "与ZK服务器断开连接");
}else if(KeeperState.AuthFailed.equals(keeperState)){
System.out.println(logPrefix + "权限检查失败");
}else if(KeeperState.Expired.equals(keeperState)){
System.out.println(logPrefix + "会话失效");
}
}
/**
* <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.createNode(PARENT_PATH, System.currentTimeMillis() + "")) {
Thread.sleep(1000);
// 读取数据
System.out.println("---------------------- read parent ----------------------------");
//zkWatch.readData(PARENT_PATH, true);
// 读取子节点,因为添加了watch事件,所以下面创建子节点时会触发watch事件
System.out.println("---------------------- read children path ----------------------------");
zkWatch.getChildren(PARENT_PATH, true);
// 更新数据
zkWatch.updateData(PARENT_PATH, System.currentTimeMillis() + "");
Thread.sleep(1000);
// 创建子节点
zkWatch.createNode(CHILDREN_PATH, System.currentTimeMillis() + "");
Thread.sleep(1000);
zkWatch.updateData(CHILDREN_PATH, System.currentTimeMillis() + "");
}
Thread.sleep(50000);
// 清理节点
zkWatch.deleteAllTestPath();
Thread.sleep(1000);
zkWatch.releaseConnection();
}
}