整个ZooKeeper的数据模型是一棵树,树上的每个节点,称为ZNode。ZNode的节点路径标识方式和Unix文件系统路径非常,都是由一系列的使用/进行分割的路径标识。
每个ZNode也是有生命周期的,其生命周期长短取决于ZNode的类型。
在谈到分布式的时候,通常说的节点是指组成集群的每台机器。然而在ZooKeeper中,节点分为两种:第一种是构成集群的机器,我们称之为机器节点;第二种是数据模型中的数据节点ZNode。
持久型(persistent)
这种类型是最常用的一种。这样的节点被创建后,应会一直存在于ZooKeeper服务器上,直到有删除操作来清除该节点。
持久顺序节点(persistent_sequential)
这种节点除了有持久性之外,还会有顺序。每个父节点都会为它的直接子节点维护一个顺序,用于记录节点的创建顺序。
基于顺序这个特性,在创建节点时,ZooKeeper会自动为给定节点名加上一个数字后缀作为新的完整的节点名。别外,数字后缀是Integer类型的,所以最大是整形的最大值。
临时节点(ephemeral)
与持久节点不同的是,临时节点是与客户端会话关联的节点。也就是会话结束(或者失效)时,临时节点也会被ZooKeeper自动的清除了。另外要注意的是,会话失效并不是TCP连接断开。
客户端建立长连接后,会话也就开始了。基于这个连接,客户端可以通过心跳检测与服务器保持有效的会话。Session的seesionTimeout用于设置一个客户端的会话超时时间。当由于服务器压力太大、网络故障或者时客户端主动断开连接等各种原因导致客户端连接断开时,只要在sessionTime规定时间内能够重新连接上集群中的任意一台服务器,那么之前创建的会话仍然有效。
临时顺序节点(ephemeral_sequential)
具有临时性和顺序性。
每个ZNode除了自身的path外,还有一个Stat:
Watcher其实是一个Event的Handler。当发生指定事件后,会执行Watcher.process()方法。
1、通过上面的描述,知道可以通过exists,getData,getChildren来注册Watcher。
其中exists、getData方法注册的Watcher时用于处理指定节点的。
Exists方法是这样描述的:
getData方法是这样描述的:
getChildren注册的方法,既处理指定节点的delete事件,也处理指定节点的子节点的create/delete事件。
2、创建会话时,指定默认的Watcher
如果了解过ZooKeeper Java Client API的话,就可以知道getData、exists、getChildren方法都有一种使用默认Watcher的调用方式:
public Stat exists(String path, boolean watch); public byte[] getData(String path, boolean watch, Stat stat); public List<String> getChildren(String path , boolean watch); |
这个boolean 标识就是表示是否使用默认的Watcher。如果是true,则表示使用默认的watcher,如果为false,则表示不注册Watcher。
指定默认的Watcher的方式有两种:
方式一:使用ZooKeeper构造器创建ZooKeeper client对象时。
方式二:在使用Zookeeper Client时,使用register方法。
另外,注册的Watcher只会存储在Client的 WatchManager中,不会发给服务端。
服务端对节点进行操作(增加子节点、删除节点、修改节点数据)后,就会发布事件。服务端处理过程如下:
上面说的Watcher,并不是一个真实的Watcher对象,因为客户端在注册Watcher时,根本就没有把Watcher序列化传输给服务端。所以这上面说的Watcher,可以理解为Client注册的Watcher的一个引用。服务端其实只是把事件发给客户端而已
客户端接收到响应后,如果响应头中replyHdr中标识了XID为-1时,就认为是一个事件通知类的响应,接下来处理过程大体分为以下4步:
需要注意的是:
在进行Watcher回调时,会从WatchManager取出与相关path关联的多个Watcher(此时WatchManager中就不会再有这个path相关的Watcher了),然后串行的调用这多个Watcher#process方法。所以在编程时,会根据业务的需要,有可能会反复注册Watcher。另外因为多个Watcher的调用是串行的,所以不要因为一个Watcher的处理逻辑影响了整个客户端的Watcher回调。
下面是我写的一个测试用例:
上述代码中,相当于模拟了两种客户端:
一种是用于操作数据的。参见 test方法。
一种是注册Watcher,根据数据变化做出响应的。参见initStrategies方法和两个Watcher实现类。