原文链接:http://www.dubby.cn/detail.html?id=9028
了解观察者
zk中所有的读操作,getData()
,getChildren()
和exists()
,都可以设置一个观察者。在zk中观察者(watch)的定义是:观察事件是一个一次性的触发器,会发送给设置了这个观察者的客户端,这个触发器只有在关注的数据改变时才会触发。对watch来说,有三个主要特性:
- 一次性触发:观察事件会在观察的数据改变时被主动发送给客户端。举个例子,如果一个客户端调用了
getData("/znode1", true)
,也就是在/znode1
上设置了一个观察者,在这之后,如果/znode1
发生了改变或者被删除了,这个客户端会受到观察事件的提醒,但是,如果之后/znode1
又发生了改变或者删除,这个客户端是不会收到事件提醒的了。 - 发送给客户端:这意味着一个观察事件也许已经在发送的路上了,但是除非发起这个改变请求的客户端真正的收到成功返回码之前,设置观察者的客户端并没有真正的收到这个观察事件提醒。zk提供了一个顺序,那就是,如果你给一个数据设置了观察者,那么,除非这个客户端已经收到了观察者事件,否则绝对不会感知到这个数据已经变化。网络延时或者一些其他的因素也许会导致不同的客户端收到观察者事件的事件不一致。但是,每个客户端收到的观察者事件提醒的顺序都是一致的。
- 观察者观察的数据:zk中一个节点发生改变会有不同的方式。也就是,在zk服务端中,维护着两个观察者列表:数据观察者、子节点观察者。
getData()
和exists()
设置的是数据观察者,getChildren()
设置的是子节点观察者。为了帮助读者理解触发的实际,在这里有必要多说几句:getData()
和exists()
返回的是节点的信息,而getChildren()
返回的是一个子节点的列表。因此,一个成功的setData()
会触发数据观察者。一个成功的create()
和delete()
操作会触发这个节点的的数据观察者和这个节点的父节点的子节点观察者。
观察者的语义
上面提到了设置观察者的三个方式,getData()
、exists()
、getChildren()
。下面列出了不同的事件类型以及对应的观察者:
- Created(创建类型):调用
exists
设置的观察者 - Deleted(删除类型):调用
exists
,getData
,getChildren
设置的观察者 - Changed(改变类型):调用
exists
,getData
设置的观察者 - Child(子节点类型):调用
getChildren
设置的观察者
观察者有哪些保证
zk给我们做了这些保证:
- 观察者事件之间的顺序都是固定的,zk客户端为了保证这些事件的顺序都是有序的。
- zk客户端是先收到观察者事件的提醒,然后才会感知到这个znode的数据的新值。
- 观察者事件的顺序是和zk服务端数据更新的顺序保持一致的。
需要注意的东西
- 观察者是一次性的;如果你收到了一次事件提醒,你还希望继续监听这个节点,那么你需要重新设置一个观察者。
- 因为观察者是一次性的,所有会有可能在你收到提醒和设置新的观察者之间,错过一些这个节点改变的提醒。也就是说你不可能监听某个节点的所有改变提醒。也许你并不关心这种情况,但至少你需要知道这是可能发生的。
- 一个观察者,只会被触发一次(我觉得有的读者可能想打我了,因为我一直在重复这一句话……),所以,如果你把一个观察者设置了几次,比如,你在getData的时候设置了这个观察者对象,然后在exists的时候,用的还是这个对象,那么,当这个节点被删除的时候,这个观察者只会被触发一次删除提醒。
- 如果zk客户端和zk服务单断开连接了,那么直到客户端重新连接之前,都不会受到观察者提醒了。所有的session的事件会被发送到session观察者。你可以利用session观察者来妥善处理你的应用逻辑。