Zookeeper监听机制杂记
概述-监听机制背景
zookeeper中的事件监听和通知机制,分布式系统中各个节点,可协调资源抢占,可了解整个集群所处状态的
概述-监听流程
image.png image.png- 首先有一个main()线程
- 在main线程中创建Zookeeper客户端,会创建两个线程,connect负责网络连接通信,listener负责监听
- 通过connect线程将注册的监听事件发送给Zookeeper
- 在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中
- Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程
- listener线程内部调用了process()方法
概念-事件和状态
zookeeper客户端连接描述 由状态和事件构成,
- 要监听zookeeper客户端状态的变化
- 要关注zookeeper客户端连接的事件
1. 状态:客户端与server连接的状态
- KeeperState.Expired
客户端和服务器在ticktime的时间周期内,是要发送心跳通知的。这是租约协议的一个实现。客户端发送request,告诉服务器其上一个租约时间,服务器收到这个请求后,告诉客户端其下一个租约时间是哪个时间点。当客户端时间戳达到最后一个租约时间,而没有收到服务器发来的任何新租约时间,即认为自己下线(此后客户端会废弃这次连接,并试图重新建立连接)。这个过期状态就是Expired状态 - KeeperState.Disconnected
就像上面那个状态所述,当客户端断开一个连接(可能是租约期满,也可能是客户端主动断开)这是客户端和服务器的连接就是Disconnected状态 - KeeperState.SyncConnected
一旦客户端和服务器的某一个节点建立连接(注意,虽然集群有多个节点,但是客户端一次连接到一个节点就行了),并完成一次version、zxid的同步,这时的客户端和服务器的连接状态就是SyncConnected - KeeperState.AuthFailed
zookeeper客户端进行连接认证失败时,发生该状态
这些状态在触发时,所记录的事件类型都是:EventType.None
2. 事件:客户端监听某个znode节点”/node-x”时用到的事件
- EventType.NodeCreated
当node-x这个节点被创建时,该事件被触发 - EventType.NodeChildrenChanged
当node-x这个节点的直接子节点被创建、被删除、子节点数据发生变更时,该事件被触发。 - EventType.NodeDataChanged
当node-x这个节点的数据发生变更时,该事件被触发 - EventType.NodeDeleted
当node-x这个节点被删除时,该事件被触发。 - EventType.None
当zookeeper客户端的连接状态发生变更时,即KeeperState.Expired、KeeperState.Disconnected、KeeperState.SyncConnected、KeeperState.AuthFailed状态切换时,描述的事件类型为EventType.None
注册监听,触发事件
- 通过zk.register(watcher)注册默认监听。
- 采用zk.getChildren(path, watch)、zk.exists(path, watch)、zk.getData(path, watcher, stat)这样的方式为某个znode注册监听。
可以触发watcher的方法:create、delete、setData。连接断开的情况下触发的watcher会丢失
image.pngZookeeper维护了两个Watch列表,一个节点数据Watch列表,另一个是子节点Watch列表。getData()和exists()设置数据Watch,getChildren()设置子节点Watch。两者选其一,可以让我们根据不同的返回结果选择不同的Watch方式,getData()和exists()返回节点的内容,getChildren()返回子节点列表。因此:
- 成功的setData()将触发正在设置的znode的数据监视(假设设置成功)。
- 成功的create() 将触发正在创建的znode的数据监视和父znode的子监视。
- 成功的delete()将触发要删除的znode的数据监视和子监视(因为不能再有子监视)以及父znode的子监视。
监听机制的特点:各家之言
一个节点可以注册多个watcher,但是分成两种情况,当一个watcher实例多次注册时,zkClient也只会通知一次;当多个不同的watcher实例都注册时,zkClient会依次进行通知(并不是很多网贴粗略说的“多次注册一次通知”),后文将会有实验。
监控同一个节点X的一个watcher实例,通过exist、getData等注册方式多次注册的,zkClient也只会通知一次。这个原理在很多网贴上也都有说明,后文我们同样进行实验。
注意,很多网贴都说zk.getData(“/node-x”,watcher)这种注册方式可以监控节点的NodeCreated事件,实际上是不行的(或者说没有意义)。当一个节点还不存在时,zk.getData这样设置的watcher是会抛出KeeperException$NoNodeException异常的,这次注册会失败,watcher也不会起作用;一旦node-x节点存在了,那这个节点的NodeCreated事件又有什么意义呢?(后文做实验)
zookeeper中并没有“永久监听”这种机制。网上所谓实现了”永久监听”的帖子,只是一种编程技巧。思路可以归为两类:一种是“在保证所有节点的watcher都被重新注册”的前提下,再进行目录、子目录的更改;另外一种是“在监听被触发后,被重新注册前,重新取一次节点的信息”确保在“监听真空期”znode没有变化。 有兴趣的读者可自行百度。
- 一次性的触发器(one-time trigger)
当数据改变的时候,Watch事件会产生并且被发送到客户端中。但是客户端只会收到一次这样的通知,如果以后这个数据再次发生改变的时候,之前设置Watch的客户端将不会再次收到改变的通知,因为Watch机制规定了它是一个一次性的触发器。
当设置监视的数据发生改变时,该监视事件会被发送到客户端。例如,如果客户端调用了 getData("/znode1", true) 并且稍后 /znode1 节点上的数据发生了改变或者被删除了,客户端将会获取到 /znode1 发生变化的监视事件,而如果 /znode1 再一次发生了变化,除非客户端再次对 /znode1 设置监视,否则客户端不会收到事件通知。
- 发送给客户端(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). 网络延迟或者其他因素可能导致不同的客户端在不同的时刻感知某一监视事件,但是不同的客户端所看到的一切具有一致的顺序。
- 被设置Watch的数据(The data for which the watch was set)
这意味着 znode 节点本身具有不同的改变方式。可以理解为Zookeeper维护了两个Watch列表,一个节点数据Watch列表,另一个是子节点Watch列表。getData()和exists()设置数据Watch,getChildren()设置子节点Watch。两者选其一,可以让我们根据不同的返回结果选择不同的Watch方式,getData()和exists()返回节点的内容,getChildren()返回子节点列表。因此:
- 成功的setData()将触发正在设置的znode的数据监视(假设设置成功)。
- 成功的create() 将触发正在创建的znode的数据监视和父znode的子监视。
- 成功的delete()将触发要删除的znode的数据监视和子监视(因为不能再有子监视)以及父znode的子监视。
- 监听真空期图示
注册监听后主动检查本次节点的znode-version和上次节点的znode-version是否一致,来确定是否真空期有节点变化。
参考
zk事件官网文档
zk事件官网翻译
https://www.jianshu.com/p/be8988f375d2
hadoop系列:zookeeper(3)——zookeeper核心原理(事件)
Zookeeper Watcher事件监听
zookeeper(四):核心原理(Watcher、事件和状态)