bigywater.tech
ZooKeeper · 109

ZooKeeper 迁移到 etcd

把数据从 ZooKeeper 搬到 etcd 并不难,难的是「搬完之后行为一样」。这篇用一份有代表性的数据(分层配置、顺序节点、临时节点、ACL)做一次真实迁移:整体拷贝并核对、列出所有搬不过去的语义,再用 watch 做切换期间的增量同步。

0.21 s
4948 个 key 整体拷贝到 etcd 并核对一致
10 个
会话关闭后 ZooKeeper 里消失、但在 etcd 里残留的临时节点数
9 ms
watch 增量同步写入 etcd 的延迟(中位),300 次变更零差异

数据与权限

实测 · 本机执行输出
  先用没有任何凭据的迁移工具遍历:读到 4943 个 znode,其中 5 个因 ACL 拒绝而读不了(NoAuthError):['/app/svc00/prod/secret0', '/app/svc00/prod/secret1']…
  给迁移工具加上 digest 凭据(add_auth)后遍历:4948 个 znode
  znode 总数 4948(不含 /zookeeper),临时节点 10,顺序节点 30,带 ACL 的节点 5;建数据用时 6.1 s

ACL 的机制见ACL 那篇:迁移账号需要对所有节点有读权限,否则漏数据时没有任何报错(我的工具专门捕获了 NoAuthError 才发现)。

整体拷贝与核对

实测 · 本机执行输出
  4948 个 key 写入 etcd,用时 0.21 s(24042 key/s)

搬不过去的语义

实测 · 本机执行输出
  路径集合相同:True(ZooKeeper 4948 / etcd 4948);数据不同的 key:0;整体 sha256 前 16 位:ZK c20e709d9a1bcee2 / etcd c20e709d9a1bcee2

临时节点的语义见那一篇:它依赖会话。etcd 里与之对应的是租约(lease),但租约要由客户端定期续约,不是搬过去就有。

目录与前缀

实测 · 本机执行输出
  版本号:ZooKeeper 里 /app/svc00/prod/key00 的 version=0(czxid=0xbe);搬到 etcd 后 version=1,mod_revision=29 —— 版本信息被重置,依赖 version 做 CAS 的客户端要重新读
  临时节点:ZooKeeper 里 /live/node* 是临时节点(属于 10 个会话);etcd 里它们现在是普通 key。关闭那 10 个 ZooKeeper 会话后:ZooKeeper 剩 0 个,etcd 里仍有 10 个 —— 要自己用租约重建「会话消失就删除」的语义
  ACL:ZooKeeper 里 5 个节点带 digest ACL;搬到 etcd 后,读回来的字段只有 key、value、version、revision,没有 ACL 信息(etcd 的授权是另一套机制,我没有配置,也就没有迁移任何访问控制)
  顺序节点:/queue/job-0000000000… 搬过去只是普通 key(序号只是名字的一部分);之后想继续「建节点时自动加序号」,要自己用事务实现,这里没有实现
  「列出 /app/svc1 的子节点」:ZooKeeper 里 /app/svc1 不存在(服务名是 svc10–svc19);etcd 用前缀 /app/svc1 查询返回 2450 个 key(把 svc10~svc19 全匹配了),用 /app/svc1/ 查询返回 0 个
  「列出 /app/svc00 的子节点」:ZooKeeper 的 get_children 返回 4 个直接子节点(['prod', 'dev', 'test', 'stg']);etcd 的前缀查询 /app/svc00/ 返回 249 个 key(整棵子树,要自己按层级过滤出直接子节点)

增量同步

实测 · 本机执行输出
  同步器为 4906 个 znode 挂了 watch(挂完用时 23.1 s)
  变更 {'删': 47, '改': 181, '建': 72};同步后 /app 子树:ZooKeeper 4931 / etcd 4931;etcd 缺少 0 个、多出 0 个、数据不同 0 个
  「修改 / 新建」从 ZooKeeper 写入返回到 etcd 里写完的延迟:中位 9 ms,最大 16 ms(253 个样本)
  差异示例: []

用 watch 追踪变化要小心第 85 篇讲过的合并:快速的连续更新可能只看到最后一次,所以这里是「读最新值再写入」,最终一致,但不保证每次变更都转发。

分步演示

ZooKeeper → etcd 迁移
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。ZooKeeper 3.9.6 单节点、etcd 3.7.2 单节点(HTTP/JSON 网关,事务每批 100 个 put);数据是脚本生成的合成数据,随机种子固定;增量同步器用 kazoo 的 DataWatch / ChildrenWatch
    • 没有验证:双写与灰度切流(读写切换顺序)、租约方式重建临时节点语义、etcd 的 RBAC 迁移 ACL、顺序节点的 etcd 实现、百万级 znode 的迁移耗时与 etcd 事务上限(单事务最多多少操作我只用了 100)、从旧 ZooKeeper 版本迁移
    京ICP备2026067502号-1京公网安备11010502063487号