ZooKeeper的系统模型
ZooKeeper系统模型
本文将从数据模型、节点特性、版本、Watcher和ACL五个方面介绍一下Zookeeper的系统模型。
数据模型
Zookeeper的数据模型结构类似于Unix的文件系统,但是没有文件系统中的目录和文件等相关概念,而是使用了“节点(ZNode)”这个概念。每个ZNode上都可以保存数据,同时可以挂靠子节点。这就让ZooKeeper的整个结构看起来像一个树型结构。每一个节点会有一些属性以及事务ID,事务ID在ZooKeeper是唯一的。当修改ZooKeeper状态的时候都会产生一个事务ID,比如创建、删除和更新节点,或者会话的创建和失效等操作。事务ID分为两部分,总长64位,高32位属于Leader节点的选举时间,低32位才是每次递增的部分。
ZNode分为4种类型:持久节点、持久顺序节点、临时节点和临时顺序节点。
- 持久节点是不会随着会话的实效而被删除的节点,除非手动删除。
- 持久顺序节点是在同一个父节点下按照序号自增顺序产生的持久节点,每一个持久顺序节点在名称上都有一个序号,这个序号在同一个父节点下是连续的。
- 临时节点的生命周期随着会话的创建而开始,随着会话的实效而消失。临时节点需要注意一点:该类型节点无法挂靠子节点。
- 临时顺序节点和持久顺序节点类似。
每一个ZNode除了可以保存数据还拥有一些属性,这些属性在ZooKeeper中被封装在Stat对象中。
| 状态属性 | 说明 |
|---|---|
| czxid | 表示该数据节点被创建时的事务 ID |
| mzxid | 表示该节点最后一次被更新时的事务 ID |
| ctime | 表示节点被创建的时间 |
| mtime | 表示该节点最后一次被更新的时间 |
| version | 数据节点的版本号。关于 ZooKeeper 中版本相关的内容,将在 7.1.3 节中做详细讲解 |
| cversion | 子节点的版本号 |
| aversion | 节点的 ACL 版本号 |
| ephemeralOwner | 创建该临时节点的会话的 sessionID。如果该节点是持久节点,那么这个属性值为 0 |
| datalength | 数据内容的长度 |
| numChildren | 当前节点的子节点个数 |
| pzxid | 表示该节点的子节点列表最后一次被修改时的事务 ID。注意,只有子节点列表变更了才会变更 pzxid,子节点内容变更不会影响 pzxid |
其中的版本是ZooKeeper中的又一个概念。每个节点都具有三种版本信息,对节点的任何更新操作都会引起版本号的变化。ZooKeeper中版本号是用来实现乐观锁的写入校验。在setData的时候,ZooKeeper会根据请求中携带的版本号和当前版本号进行对比,如果携带的版本号和节点当前的版本号不一致,则会抛出异常。
Watcher机制
ZooKeeper支持接听节点,当节点处于被监听的某种状态时会执行相对应的逻辑。这套Watcher机制包括客户端线程、客户端WatchManager和ZooKeeper服务器三部分。客户端向ZooKeeper注册Watcher的同时,会将Watcher对象存储在客户端的WatchManager中。当ZooKeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatchManeger中取出对应的Watcher对象来执行回调逻辑。
-
客户端注册Watcher
- 使用ZooKeeper的原生客户端,在new客户端的时候,可以传入一个Watcher作为当前客户端默认的Watcher。
- getData、getChildren和exist这三个接口也可以向ZooKeeper服务器注册Watcher。注册的方式有两种一种是告诉ZooKeeper当前节点使用默认的Watcher;另一种则是注册自定义的Watcher。
- 如果通过上面三个接口注册,则在发送请求是,客户端会将请求标记为“使用Watcher监听”,同时封装一个WatcherRegistration对象,用户暂存节点路径和Watcher的对应关系。
- 上面提到的这个对象最终会被封装成一个Packet对象,Packet可以被看作是ZooKeeper中最小的通信协议单元,用于进行客户端与服务端之间的网络传输。最后被添加进发送队列中等待客户端发送。
- 发送完成后会由客户端的sendThread线程的readResponse方法负责接收来自服务端的响应,finshPacket方法会从Packet中取出对应的Watcher并注册到ZKWatcherManager中。
- Packet中封装的WatcherRegistration对象并不会被发送到服务端,Packet在进行序列化的时候只会将其中的requestHeader和request属性进行序列化。
-
服务端处理Watcher
- 服务端处理Watcher注册的核心是WatchManager。
- WatchManager内部包含了watchTable和watch2Paths两部分。其中watchTable是从数据节点路径的粒度来托管Watcher;watch2Paths是从Watcher的粒度来控制事件触发需要触发的数据节点。
- WatchManager负责事件的触发,并移除那些已经触发的Watcher,最后向客户端发送WatcherEvent。
-
客户端回调Watcher
- 服务端的响应,客户端都是由SendThread.readResponse()方法来统一进行处理的。如果响应头replyHdr中标识了XID为-1,表明这是一个通知类型的响应。
- 接收到通知类型的响应后,客户端首先进行反序列化,将字节流转换为WatcherEvent对象;其次根据客户端的chrootPath属性,将服务端返回的绝对路径转换为相对路径;最后将回传的WatcherEvent转换成WatchedEvent,并将该event对象发送给EventThread线程,在下一个轮询周期中进行Watcher回调。
- 客户端中的watcher也是一次性的,触发后就会被移除。

浙公网安备 33010602011771号