Zookeeper 的数据结构是怎样的?

#中间件#面试八股

Zookeeper 的数据结构是怎样的?

结论先行:ZK 的数据模型是一棵内存中的树,每个节点叫 znode,路径唯一(如 /app/config/db),节点上可挂少量数据(默认上限 1MB)和一份 stat 元信息。节点分持久、临时、顺序、容器、TTL 五类,版本号(version/cversion)支撑 CAS 式更新。

一、树形结构

/
├── app
│   ├── config
│   │   ├── db        data="jdbc:mysql://..."  version=3
│   │   └── cache     data="redis://..."       version=1
│   └── locks
│       ├── lock-0000000001   (临时顺序)
│       └── lock-0000000002
└── zookeeper
    └── config        (系统保留)

注意:znode 不是目录,它既能像目录一样有子节点,也能自己存数据,这与文件系统不同。

二、五种节点类型

类型创建方式生命周期典型用途
持久节点create手动删除才消失配置、元数据
临时节点create -e会话结束自动删除存活标识、注册中心
持久顺序节点create -s手动删除,名字自动加序号队列、锁排队
临时顺序节点create -e -s会话结束删除,名字带序号分布式锁、选主
容器节点create -c子节点为空时由服务端清理批量任务分组

临时节点不能有子节点,这是硬约束,也是“临时目录”方案行不通的原因。

三、stat 里有什么

字段含义
czxid创建该节点的事务 ID
mzxid最后一次修改数据的事务 ID
pzxid最后一次修改子节点列表的事务 ID
ctime / mtime创建 / 修改时间
version数据版本,每次 setData 加一
cversion子节点版本,子节点增删时加一,顺序号的来源
aversionACL 版本
ephemeralOwner临时节点所属会话 ID,持久节点为 0
dataLength / numChildren数据长度与子节点数

四、CAS 式更新

// version = -1 表示不校验;指定版本则版本不符时抛 BadVersionException
Stat stat = new Stat();
byte[] old = client.getData().storingStatIn(stat).forPath("/app/config/db");

client.setData()
      .withVersion(stat.getVersion())
      .forPath("/app/config/db", "jdbc:mysql://new".getBytes());

这等价于乐观锁:两个客户端同时读到 version=3,只有一个能写成功,另一个必须重读重试。

# 命令行同样支持版本
set /app/config/db "v2" 3

常见追问

  • 追问:为什么限制 1MB?数据要全量加载进内存并在集群间同步,越大越拖慢写与恢复;需要存大对象时应存到对象存储,ZK 里只放引用。
  • 追问:顺序号会重复吗?不会。序号由父节点的 cversion 生成,同一父节点下的创建在 Leader 上串行执行。
  • 追问:顺序号连续吗?不一定。创建失败、会话断开重试都会消耗序号,所以只能保证单调递增,不能当连续 ID 用。
  • 追问:数据存在磁盘还是内存?全量在内存,磁盘上只存事务日志与周期性快照,所以 ZK 的堆大小决定了能承载的数据量。

小结

  • 树形命名空间 + 节点数据 + stat 元信息,是 ZK 的全部数据模型。
  • 五种节点类型由“是否持久”和“是否顺序”组合而来。
  • version 提供 CAS 语义,cversion 提供顺序号。
  • 1MB 上限与内存存储决定了它只能放元数据。
笔记加载中…