我们都知道 zookeeper 使用了树状结构来管理它的 ZNode, 无数中间件使用 zookeeper 的 ZNode 组织并管理了具有层次关系的数据结构;
本文将探究 zookeeper ZNode 树状结构的实现方式及原理;
在 ZooKeeper 中,ZNode 是数据存储的基本单元,每个 ZNode 可以存储数据并关联一组子节点; ZNode 的具体数据结构设计得非常简洁,但功能强大,能够支持 ZooKeeper 的核心功能,如数据存储、事件通知和分布式协调;
ZNode 的数据结构
ZNode 的数据结构主要包括以下字段:
data
- 类型:
byte[]
(字节数组) - 作用:存储 ZNode 的实际数据。
- 说明:ZooKeeper 不限制数据的格式,用户可以存储任意二进制数据。
acl
- 类型:
List<ACL>
(访问控制列表) - 作用:定义 ZNode 的访问权限。
- 说明:ACL 包含权限(如读、写、创建、删除等)和授权对象(如用户、IP 地址等)。
stat
- 类型:
Stat
(状态信息) - 作用:存储 ZNode 的元数据信息。
- 说明:
Stat
结构包含以下字段:- czxid:创建该 ZNode 的事务 ID(ZXID)
- mzxid:最后一次修改该 ZNode 的事务 ID(ZXID)
- pzxid:最后一次修改子节点的事务 ID(ZXID)
- ctime:ZNode 的创建时间(毫秒)。
- mtime:ZNode 的最后修改时间(毫秒)。
- version:ZNode 的数据版本号(每次数据更新时递增)。
- cversion:ZNode 的子节点版本号(每次子节点变化时递增)。
- aversion:ZNode 的 ACL 版本号(每次 ACL 更新时递增)。
- ephemeralOwner:如果 ZNode 是临时节点,存储其所有者的会话 ID;否则为 0。
- dataLength:ZNode 数据的长度。
- numChildren:ZNode 的子节点数量。
children
- 类型:
Set<String>
(子节点集合) - 作用:存储 ZNode 的子节点名称;
- 说明:ZNode 可以包含多个子节点,形成树状结构;
假设有一个 ZNode /app/config
,其数据结构可能如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16data: "key=value" (二进制数据)
acl: [{"perms": 31, "id": {"scheme": "world", "id": "anyone"}}] (允许所有用户读写)
stat: {
czxid: 0x100000001,
mzxid: 0x100000002,
pzxid: 0x100000001,
ctime: 1698765432000,
mtime: 1698765433000,
version: 1,
cversion: 0,
aversion: 1,
ephemeralOwner: 0,
dataLength: 9,
numChildren: 0
}
children: ["/app/config/boot", "/app/config/runtime"]
ZNode 的索引机制
DataTree
ZooKeeper 使用对象 DataTree 来存储所有的 ZNode:
Map<String, DataNode>
:String
是 ZNode 的路径(如/app/config
);DataNode
是 ZNode 的实际数据;
假设 DataTree 中有以下 ZNode:1
2
3
4
5
6
7/
/app
/app/config
/app/logs
/users
/users/alice
/users/bob
DataTree 的内部存储如下:1
2
3
4
5
6
7
8Map<String, DataNode>:
"/" -> DataNode {data: null, children: ["/app", "/users"]}
"/app" -> DataNode {data: null, children: ["/app/config", "/app/logs"]}
"/app/config" -> DataNode {data: "key=value", children: []}
"/app/logs" -> DataNode {data: null, children: []}
"/users" -> DataNode {data: null, children: ["/users/alice", "/users/bob"]}
"/users/alice" -> DataNode {data: "role=admin", children: []}
"/users/bob" -> DataNode {data: "role=user", children: []}
如需遍历整个 ZNode 树, 只需递归获取 children 后再次从 DataTree 查询即可;
临时节点管理
Map<Long, Set<String>>
:Long
是客户端会话 ID。Set<String>
是该会话创建的所有临时节点的路径。
- 特点:
- 用于管理临时节点,当会话结束时自动删除相关临时节点。
Watcher 管理
Map<String, Set<Watcher>>
:String
是 ZNode 的路径。Set<Watcher>
是监听该 ZNode 的所有 Watcher。
- 特点:
- 用于实现事件通知机制,当 ZNode 发生变化时触发 Watcher。
举例如下:
- 客户端变更
- 客户端调用 create(“/app/config”, data);
- zk 在 /app 下创建子节点 /app/config;
- DataTree 更新
- ZooKeeper 更新 /app 的以下字段:
- cversion:递增;
- pzxid:更新为当前事务的 ZXID;
- children:将 /app/config 添加到子节点集合中;
- ZooKeeper 更新 /app 的以下字段:
- Watcher 触发
- 根据路径 /app/config 解析出其父节点为 /app;
- 如果客户端注册了监听 /app 的 children 变更的 Watcher, ZooKeeper 会执行以下步骤:
- 从 Watcher 管理 Map 中查找 /app 对应的 Watcher 集合;
- 遍历 Watcher 集合, 依次触发每个 Watcher 的 process(WatchedEvent event) 方法, 其中事件类型为 EventType.NodeChildrenChanged;