bigywater.tech
ZooKeeper · 10

内存 DataTree

ZooKeeper 把整棵树放在内存里,这是它读得快、也是它不适合存大量数据的原因。这篇创建到 20 万个 znode,量 JVM 堆和进程内存是怎么随 znode 数增长的、读延迟是多少、重启把树读回内存要多久。

0.52 KiB
每个 znode(含 200 字节数据)占用的 JVM 堆,由 200006 个 znode 的增量算出
约 0.02 ms
服务端统计的读延迟(清零统计后随机读 20000 次)
840 ms
200006 个 znode 的节点重启到能响应

内存随 znode 数增长

实测 · 本机执行输出
    znode 数       近似数据大小     JVM 已用堆    进程 RSS
          6       0.0 MiB      6.0 MiB     87 MiB
      10006       2.0 MiB     11.5 MiB    106 MiB
      50006      10.0 MiB     32.4 MiB    160 MiB
     100006      20.0 MiB     58.4 MiB    209 MiB
     200006      40.1 MiB    108.5 MiB    257 MiB

mntr 的 zk_approximate_data_size 只估算了路径和数据的大小(约 40 MiB),真正的 JVM 堆占用大得多,因为每个 znode 还有对象头、stat 结构、父子关系等开销。容量规划(第 60 篇)要用实测的堆占用,不是数据大小。(这里的「已用堆」每次都先手动触发了一次 GC;JVM 堆上限我设成 256 MiB。)

读不走磁盘

实测 · 本机执行输出
  srvr 的 Latency min/avg/max(毫秒): 0/0.0192/1

重启重建整棵树

实测 · 本机执行输出
  停机时 data/version-2 里快照文件总大小 50.9 MiB,事务日志实际占盘 58.6 MiB(没有触发快照时数据主要在事务日志里)
  日志: Reading snapshot …/snapshot.2d8eb
  日志: Snapshot loaded in 412 ms, highest zxid is 0x30d43, digest is 428321081763740
  日志: Snapshot taken in 134 ms
  重启到能响应 840 ms;重启后 znode 数 200006

数据越多,重启越慢,选举后 follower 同步(第 42 篇)也越慢。快照加载日志里的耗时就是把树读回内存的时间。

分步演示

内存里的树
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。JVM 堆上限调成 256 MiB;znode 路径形如 /d/k000001,数据 200 字节,用 kazoo 异步创建;snapCount=200000;已用堆用 jcmd GC.run + GC.heap_info 取得
    • 没有验证:更深的路径层级、更小或更大的数据对每个 znode 开销的影响、G1 / ZGC 等不同垃圾回收器、堆接近上限时的行为(第 62 篇看 GC 停顿)、集群里 follower 的内存
    京ICP备2026067502号-1京公网安备11010502063487号