把数据从 ZooKeeper 搬到 etcd 并不难,难的是「搬完之后行为一样」。这篇用一份有代表性的数据(分层配置、顺序节点、临时节点、ACL)做一次真实迁移:整体拷贝并核对、列出所有搬不过去的语义,再用 watch 做切换期间的增量同步。
先用没有任何凭据的迁移工具遍历:读到 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 篇讲过的合并:快速的连续更新可能只看到最后一次,所以这里是「读最新值再写入」,最终一致,但不保证每次变更都转发。