🌺The Begin🌺点点关注,收藏不迷路🌺

摘要:ZNode是ZooKeeper中最基础也是最重要的概念,理解ZNode的结构和属性是掌握ZooKeeper的关键。本文将深入剖析ZNode的物理结构、逻辑组织、节点类型以及详细的Stat属性,通过流程图和源码分析,帮助读者全面理解这个分布式协调的核心数据模型。

一、ZNode概述

1.1 什么是ZNode?

ZNode(ZooKeeper Node)是ZooKeeper中数据模型的基本单元,类似于文件系统中的文件或目录。ZooKeeper使用一种树形结构的命名空间来组织这些ZNode。

/ (根节点)

/app

/services

/config

/app/config

/app/status

/services/gateway

/services/user

/config/database

/config/redis

1.2 ZNode的核心特性

特性 说明
路径标识 每个ZNode通过绝对路径唯一标识,如 /app/config
数据存储 可存储数据,默认最大1MB,建议<1KB
原子操作 读操作获取完整数据,写操作替换全部数据
兼具文件/目录特性 既可以存储数据(像文件),也可以有子节点(像目录)
节点类型 持久/临时/顺序/容器四种类型
版本控制 每个节点都有数据版本、子节点版本、ACL版本

二、ZNode的物理结构

2.1 ZNode的组成部分

每个ZNode由三个核心部分组成:

ZNode结构

ZNode

data: 业务数据

stat: 状态信息

acl: 访问控制列表

children: 子节点列表

组成部分 说明 存储内容
data 业务数据 字节数组,可存储任何序列化数据
stat 状态信息 版本号、时间戳、事务ID等元数据
acl 访问控制 定义谁可以对该节点执行什么操作
children 子节点列表 当前节点的直接子节点路径

2.2 源码中的ZNode表示

在ZooKeeper源码中,ZNode由DataNode类表示:

// ZooKeeper源码:org.apache.zookeeper.server.DataNode
public class DataNode {
    // 节点数据(字节数组)
    volatile byte[] data;
    
    // ACL权限(引用计数方式)
    Long acl;
    
    // 节点状态(持久化信息)
    private volatile StatPersisted stat;
    
    // 子节点集合(路径名 -> 不包含完整路径)
    private Set<String> children = new HashSet<>();
    
    // 父节点引用(用于快速路径查找)
    private DataNode parent;
    
    // 构造函数
    public DataNode() {
        this.data = new byte[0];
        this.stat = new StatPersisted();
        this.children = new HashSet<>();
    }
    
    // 添加子节点
    public synchronized boolean addChild(String child) {
        return children.add(child);
    }
    
    // 删除子节点
    public synchronized boolean removeChild(String child) {
        return children.remove(child);
    }
    
    // 获取子节点列表(拷贝)
    public synchronized Set<String> getChildren() {
        return new HashSet<>(children);
    }
    
    // 复制节点状态
    public void copyStat(Stat to) {
        stat.copy(to);
    }
}

2.3 内存数据结构

ZooKeeper将所有ZNode存储在内存中,使用ConcurrentHashMap进行管理:

// DataTree类中的节点存储
public class DataTree {
    // 路径 -> DataNode 的映射
    private final ConcurrentHashMap<String, DataNode> nodes = 
        new ConcurrentHashMap<>();
    
    // 临时节点索引:会话ID -> 该会话创建的所有临时节点
    private final ConcurrentHashMap<Long, HashSet<String>> ephemerals = 
        new ConcurrentHashMap<>();
    
    // 根节点
    private static final String rootZookeeper = "/";
    
    // 获取节点
    public DataNode getNode(String path) {
        return nodes.get(path);
    }
    
    // 创建节点
    public void createNode(String path, byte[] data, List<ACL> acl, 
                           long ephemeralOwner, int parentCVersion, 
                           long zxid, long time) {
        // ... 创建逻辑
    }
}

三、ZNode的逻辑结构

3.1 路径命名规则

ZNode的路径必须遵循以下规则:

规则 正确示例 错误示例
必须以/开头 /app/config app/config
不能有连续/ /app/config /app//config
不能以.开头 /app/config /app/.config
区分大小写 /App/app不同 -
长度不限 - 建议不要太长

3.2 节点层级关系

ZooKeeper ZNode层级树

三级节点

二级节点

一级节点

根节点

/services/api/instance-001
服务实例

/ (根节点)
持久节点

/app
应用根节点

/services
服务根节点

/app/config
应用配置

/app/status
应用状态

/services/api
API服务

/services/user
用户服务

层级特性

  • 根节点:只有一个根节点 /
  • 父节点:必须先存在才能创建子节点
  • 子节点:可以有多层嵌套
  • 路径深度:理论上无限制,但建议不要太深

四、ZNode的类型

ZNode在创建时确定类型,之后不可更改

4.1 四种基本类型

类型 创建参数 生命周期 是否有子节点 典型应用
持久节点 PERSISTENT 直到显式删除 可以 配置信息、元数据
临时节点 EPHEMERAL 会话结束自动删除 不能有子节点 服务注册、心跳检测
持久顺序节点 PERSISTENT_SEQUENTIAL 持久存在,带递增序号 可以 任务队列、事件顺序
临时顺序节点 EPHEMERAL_SEQUENTIAL 会话结束自动删除,带序号 不能有子节点 分布式锁、Leader选举

4.2 顺序节点的命名规则

# 创建顺序节点
[zk: localhost:2181(CONNECTED) 0] create -s /task/task- "data"
Created /task/task-0000000001

[zk: localhost:2181(CONNECTED) 1] create -s /task/task- "data"
Created /task/task-0000000002

# 命名格式:指定路径 + 10位十进制数
# /task/task-0000000001
# /task/task-0000000002
# 序号从1开始,自动递增

4.3 节点类型判断

通过ephemeralOwner字段判断节点类型:

# 创建持久节点
[zk: localhost:2181(CONNECTED) 0] create /persistent "data"
Created /persistent

[zk: localhost:2181(CONNECTED) 1] stat /persistent | grep ephemeralOwner
ephemeralOwner = 0x0  # 0表示持久节点

# 创建临时节点
[zk: localhost:2181(CONNECTED) 2] create -e /ephemeral "temp"
Created /ephemeral

[zk: localhost:2181(CONNECTED) 3] stat /ephemeral | grep ephemeralOwner
ephemeralOwner = 0x100001d6d2d0003  # 非0表示临时节点,值为会话ID

五、ZNode的Stat属性详解

Stat属性记录了ZNode的所有状态信息,是理解ZNode行为的关键。

5.1 Stat属性完整列表

// ZooKeeper源码:org.apache.zookeeper.data.Stat
public class Stat {
    // 创建节点的事务ID
    private long czxid;
    
    // 最后修改节点的事务ID
    private long mzxid;
    
    // 最后修改子节点的事务ID
    private long pzxid;
    
    // 创建时间(毫秒)
    private long ctime;
    
    // 最后修改时间(毫秒)
    private long mtime;
    
    // 数据版本号(每修改一次+1)
    private int version;
    
    // 子节点版本号
    private int cversion;
    
    // ACL版本号
    private int aversion;
    
    // 临时节点所有者会话ID(持久节点为0)
    private long ephemeralOwner;
    
    // 数据长度(字节)
    private int dataLength;
    
    // 子节点数量
    private int numChildren;
}

5.2 通过命令行查看Stat

[zk: localhost:2181(CONNECTED) 0] stat /config

# 输出示例及解释:
cZxid = 0x100000002           # 创建时的事务ID
ctime = Thu Jan 01 08:00:00 CST 1970  # 创建时间
mZxid = 0x100000005           # 最后修改的事务ID
mtime = Thu Jan 01 08:00:02 CST 1970  # 最后修改时间
pZxid = 0x100000008           # 子节点最后修改的事务ID
cversion = 3                  # 子节点版本号(已修改3次)
dataVersion = 2               # 数据版本号(已修改2次)
aclVersion = 0                # ACL版本号(未修改过)
ephemeralOwner = 0x0          # 临时节点所有者(0表示持久节点)
dataLength = 42               # 数据长度42字节
numChildren = 3               # 有3个子节点

5.3 各个字段详细说明

字段 名称 说明 更新时机 应用场景
czxid 创建事务ID 创建节点的事务ID 节点创建时 确定节点创建顺序
ctime 创建时间 节点创建的时间戳 节点创建时 监控节点存活时间
mzxid 修改事务ID 最后修改节点的事务ID 数据变更时 确定数据修改顺序
mtime 修改时间 最后修改的时间戳 数据变更时 监控数据变更频率
pzxid 子节点事务ID 最后修改子节点列表的事务ID 子节点新增/删除时 监控子节点变化
dataVersion 数据版本号 数据修改次数 每次set操作+1 乐观锁实现并发控制
cversion 子节点版本号 子节点列表修改次数 子节点新增/删除+1 监控子节点变化
aversion ACL版本号 ACL修改次数 每次setAcl操作+1 权限变更跟踪
ephemeralOwner 临时节点所有者 创建节点的会话ID 节点创建时 判断节点类型,定位所有者
dataLength 数据长度 数据字节数 数据变更时 监控数据大小,防止超限
numChildren 子节点数量 直接子节点个数 子节点变更时 监控节点负载

5.4 版本号的乐观锁应用

// 使用dataVersion实现乐观锁
public boolean updateWithVersion(String path, String newData) throws Exception {
    while (true) {
        // 1. 获取当前数据和版本号
        Stat stat = new Stat();
        byte[] data = zk.getData(path, false, stat);
        int currentVersion = stat.getVersion();
        
        // 2. 业务处理
        String processed = processData(new String(data), newData);
        
        try {
            // 3. 使用版本号更新
            zk.setData(path, processed.getBytes(), currentVersion);
            return true; // 更新成功
        } catch (KeeperException.BadVersionException e) {
            // 4. 版本冲突,重试
            System.out.println("版本冲突,重试中...");
            continue;
        }
    }
}

六、ACL属性

ACL(Access Control List)定义了谁可以对该节点执行什么操作。

6.1 ACL组成

ACL结构

ACL

scheme: 授权方案

id: 授权ID

perms: 权限位

6.2 权限类型

权限 缩写 数字表示 说明
CREATE c 1 允许创建子节点
DELETE d 2 允许删除子节点
READ r 4 允许读取节点数据
WRITE w 8 允许修改节点数据
ADMIN a 16 允许设置ACL

6.3 常见的Scheme

Scheme 说明 示例
world 全世界 world:anyone:r
auth 已认证用户 auth:user:cdrwa
digest 用户名密码认证 digest:user:password:cdrwa
ip IP地址限制 ip:192.168.1.0/24:r

七、ZNode的操作

7.1 核心操作命令

命令 语法 作用
create create [-s] [-e] path data 创建节点
delete delete path [version] 删除节点
set set path data [version] 设置节点数据
get get path [watch] 获取节点数据+状态
stat stat path [watch] 获取节点状态
ls ls path [watch] 列出子节点

7.2 创建节点示例

# 创建持久节点
create /config "db-config"

# 创建临时节点
create -e /temp "temporary"

# 创建顺序节点
create -s /seq "sequential"

# 创建临时顺序节点
create -e -s /ephemeral-seq "tmp-seq"

八、总结

8.1 ZNode结构要点

  1. 物理结构DataNode包含data、stat、acl、children四部分,存储在内存的ConcurrentHashMap
  2. 逻辑结构:树形命名空间,路径唯一标识,父节点必须先存在
  3. 节点类型:持久/临时/顺序四种类型,创建后不可更改
  4. Stat属性:包含11个字段,记录节点完整生命周期
  5. 版本控制:dataVersion/cversion/aversion实现乐观锁
  6. ACL权限:控制节点访问权限,保障数据安全

8.2 Stat属性速查表

字段 用途 检查命令
dataVersion 乐观锁并发控制 stat path | grep dataVersion
ephemeralOwner 判断节点类型 stat path | grep ephemeralOwner
numChildren 监控节点负载 stat path | grep numChildren
dataLength 监控数据大小 stat path | grep dataLength
ctime/mtime 监控节点存活 stat path | grep time

8.3 一句话总结

ZNode是ZooKeeper中集数据存储、状态管理、权限控制于一体的基础单元,其精妙的结构设计使得ZooKeeper能够在保证强一致性的同时,高效地管理分布式系统中的元数据和协调状态。

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐