一、zk的watcher机制的过程:
1、客户端注册watcher
2、服务端注册watcher
3、服务端触发watcher事件
3、客户端回调watcher
二、客户端注册watcher有三种方式,可以调用getData,exists、getChildred实现
public Logger logger = LoggerFactory.getLogger(WatcherDemo.class);
//zookeeper连接地址
private static String ZOOKEEPER_PATH = "localhost:2181";
static ZooKeeper zooKeeper;
//初始化连接
static {
try {
//创建一个 ZooKeeper 客户端对象实例时,可以向构造方法中传入一个默认的 Watcher
zooKeeper = new ZooKeeper(ZOOKEEPER_PATH, 4000, new WatcherDemo());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void process(WatchedEvent watchedEvent) {
logger.info("eventType:"+watchedEvent.getType());
if(watchedEvent.getType() == Event.EventType.NodeDataChanged) {
try {
//客户端注册watcher
zooKeeper.exists(watchedEvent.getPath(), true);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//测试
public static void main(String[] args) throws KeeperException, InterruptedException, IOException {
//读取配置的文件路径
String CONF_PATH = "/zookeeper/watch";
if (zooKeeper.exists(CONF_PATH, false) == null) {
zooKeeper.create(CONF_PATH, "0".getBytes("utf-8"), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
Thread.sleep(1000);
System.out.println("-------------");
Stat stat = zooKeeper.exists(CONF_PATH, true);
System.in.read();
}
执行控制台显示:
08:20:25.423 [main-EventThread] INFO com.cxg.common.WatcherDemo - eventType:None
接下来服务端触发事件:
ls /zookeeper/
delete /zookeeper/watch
接下来控制台继续输出:
08:20:26.223 [main-EventThread] INFO com.cxg.common.WatcherDemo - eventType:NodeDeleted
这样就完成了以上的Watcher的基本过程;那么在源码中是怎么实现的呢?
三、就例子中的exists方法来深入剖析watcher原理,getData,getChildred实现基本一致
首先我们要看看watcher的几种事件类型(这里我们可以优雅的利用枚举来实现我们在代码中常用的错误代码):
public enum EventType {
None (-1),
NodeCreated (1),
NodeDeleted (2),
NodeDataChanged (3),
NodeChildrenChanged (4);
private final int intValue; // Integer representation of value
// for sending over wire
EventType(int intValue) {
this.intValue = intValue;
}
public int getIntValue() {
return intValue;
}
public static EventType fromInt(int intValue) {
switch(intValue) {
case -1: return EventType.None;
case 1: return EventType.NodeCreated;
case 2: return EventType.NodeDeleted;
case 3: return EventType.NodeDataChanged;
case 4: return EventType.NodeChildrenChanged;
default:
throw new RuntimeException("Invalid integer value for conversion to EventType");
}
}
}
四、接下来看看exists究竟干了啥:
- 检查下路径是否正确
- 这里将watcher和clientPath捆绑到WatchRegistration对象中备用
- 设置请求头
- 设置注册API
- 封装成request请求
- 重点中的重点:没有将watcher封装,而是仅仅做了个有没有watcher的标记
- 然后使用ClientCnxn.submitRequest方法将现存的这些信息进一步封装
public Stat exists(final String path, Watcher watcher)
throws KeeperException, InterruptedException
{
final String clientPath = path;
//检查下路径是否正确
PathUtils.validatePath(clientPath);
// the watch contains the un-chroot path
//重点:这里将watcher和clientPath捆绑到WatchRegistration对象中备用
WatchRegistration wcb = null;
if (watcher != null) {
wcb = new ExistsWatchRegistration(watcher, clientPath);
}
final String serverPath = prependChroot(clientPath);
//重点
//设置请求头
RequestHeader h = new RequestHeader();
//设置注册API
h.setType(ZooDefs