因为zookeeper是以远程服务提供访问的,所以每次客户端想得到一个znode节点的内容时代价是很大的:这将带来很高的延迟同时相比于zookeeper的安装过程它需要更多的操作。考虑如图2-2所示的例子。第二个调用在\/tasks上调用getChildren将返回同样的值,一个空的集会,结果自然的然并卵的。
图 2-2 在同一个znode上执行多次读
这是轮询方式的一个常见问题。为了替代客户端轮询,我们采用了一种基于通知的机制:客户端向zookeeper注册它要接收关于znode节点的发生变化的通知。注册接收一个给定的znode上的通知叫做设置监视器。一个监视器是一次性的操作,意味着它只能触发一次通知。为了接收多个通知,客户端必须在接收通知时设置一个新的监视器。如图2-3所描述的那样,客户端只有在接收到表明\/tasks值发生变化的时候才会去读取一个新的值。
图2-3 利用通知来告知znode的变化
当使用通知时,有些事情需要注意。因为通知是一次性操作,有可能新的变化正好发生在接收通知和设置监视器之间(放心,你不会错过状态的变化)。让我们看一个例子来了解它是如果工作的。假设以下事件按顺序发生:
- 客户端C1在\/tasks的节点上设置了数据的监视器。
- 客户端C2在\/tasks下新增了一个任务。
- 客户端C1接收到了通知。
- 客户端C1设置一个新的监视器,但是在这之前,第三个客户端C3在\/tasks下新增了一个任务。
客户端C1z最终设置好了这个监视器,但是通知没有因为C3新增任务导致被触发。为了捕捉的这种变化,C1必须去读取\/tasks的状态。它确实确实这样做了,因为当设置监视器时,我们设置的是带有读取zookeeper状态的操作。自然,C1不会错过这种变化。
通知一个重要的保证是在任何发生在znode上的变化生效之前它会传递到客户端。如果一个客户端在一个znode上设置了监视器,同时有两个连续的在这个znode上的更新操作,那么客户端接收到通知发生在第一次更新之后和在它有机会通过读取znode来捕获第二次更新之前。关键点是通知保留了客户端观察到的发生顺序。虽然zookeeper状态变化传播到任意指定的客户端会变慢,但是我们保证客户端观察到zookeeper的状态变化按一个全局的顺序发生的。
Zookeeper提供不同类型的通知,这取决于监视器是如何回应设置的通知的。一个客户端可以设置一个znode数据变化的通知、子节点变化的通知和znode被创建和删除的通知。为了设置一个监视器,我们能使用API里面任意一个带读取zookeeper状态的调用。这些API调用给了一个可选参数Watcher对象,或者使用默认的监视器。在本章后面的master-worker的例子的讨论中和第四章,我们介绍了详细的使用该机制的细节。