zk作为一款成熟的分布式协调框架,订阅-发布功能是很重要的一个。所谓订阅发布功能,其实说白了就是观察者模式。观察者会订阅一些感兴趣的主题,然后这些主题一旦变化了,就会自动通知到这些观察者。
zk的订阅发布也就是watch机制,是一个轻量级的设计。因为它采用了一种推拉结合的模式。一旦服务端感知主题变了,那么只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容,所以事件本身是轻量级的,这就是所谓的“推”部分。然后,收到变更通知的客户端需要自己去拉变更的数据,这就是“拉”部分。
watch机制
一、目标是谁?
zode /path (节点操作来触发watch)
1、创建
2、修改
3、删除
4、查询
5、是否存在
二、使用
可以注册watcher的方法:getData、exists、getChildren。
public static void main(String[] args) throws KeeperException {
try {
// 连接
ZooKeeper connect = zookeeperConnection.connect("192.168.44.5:2181");
// 注册watch
connect.getData("/alex",new GetMyWatch(),connect.exists("/alex",false));
// 因为是main方法,使用此方法保证线程不被停止
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 自定义监听器
*/
private static class GetMyWatch implements Watcher{
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println(watchedEvent.getState());
System.out.println("getData 数据回调");
}
}
可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失。
修改此节点,观察watch是否被调用
可以观察到watch 被调用了,但是在这里有一个注意的地方,watch是一次性的,只会被调用一次,你在后面再次修改节点,watch是不会被调用的
zookeeper Watcher机制的特点
1、一次性的触发器(one-time trigger)
当数据改变的时候,Watch事件会产生并且被发送到客户端中。但是客户端只会收到一次这样的通知,如果以后这个数据再次发生改变的时候,之前设置Watch的客户端将不会再次收到改变的通知,因为Watch机制规定了它是一个一次性的触发器。当设置监视的数据发生改变时,该监视事件会被发送到客户端。例如,如果客户端调用了 getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。
2、发送给客户端(Sent to the client)
这个表明了Watch的通知事件是从服务器发送给客户端的,是异步的,这就表明不同的客户端收到的Watch的时间可能不同,但是ZooKeeper有保证:当一个客户端在看到Watch事件之前是不会看到结点数据的变化的。例如:A=3,此时在上面设置了一次Watch,如果A突然变成4了,那么客户端会先收到Watch事件的通知,然后才会看到A=4。Zookeeper 客户端和服务端是通过 Socket 进行通信的,由于网络存在故障,所以监视事件很有可能不会成功地到达客户端,监视事件是异步发送至监视者的,Zookeeper 本身提供了保序性(ordering guarantee):即客户端只有首先看到了监视事件后,才会感知到它所设置监视的 znode 发生了变化(a client will never see a change for which it has set a watch until it first sees the watch event). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
3、被设置Watch的数据(The data for which the watch was set)
这意味着 znode 节点本身具有不同的改变方式。你也可以想象 Zookeeper 维护了两条监视链表: 数据监视和子节点监视(data watches and child watches) 。getData() and exists() 设置数据监视,getChildren() 设置子节点监视。 或者,你也可以想象 Zookeeper 设置的不同监视返回不同的数据,getData() 和 exists() 返回 znode 节点的相关信息,而 getChildren() 返回子节点列表。因此, setData() 会触发设置在某一节点上所设置的数据监视(假定数据设置成功),而一次成功的 create() 操作则会出发当前节点上所设置的数据监视以及父节点的子节点监视。一次成功的 delete() 操作将会触发当前节点的数据监视和子节点监视事件,同时也会触发该节点父节点的child watch。