bigywater.tech
ZooKeeper · 86

配置热更新的版本与回滚

配置能热更新之后,下一批问题是:两个人同时改怎么办、改错了怎么回滚、一次改多项如何不让应用看到中间状态。这篇用 ZooKeeper 的 version 做乐观锁,自己存历史做回滚,并量一量把相关配置拆成两个节点会读到什么。

35 → 200
10 客户端累加 200 次:不带 version vs 带 version 重试
36 ms
回滚到旧版本、50 个实例全部收到的时间
500 / 1372
两个节点分两次读时读到 a≠b 的次数

version 与乐观锁

实测 · 本机执行输出
  A、B 都读到 version = 0
  A 带 version=0 写入成功
  B 也带 version=0 写入: BadVersionError(被拒绝)
  现在 version = 1,内容 = {"timeout": 60, "retries": 3}(B 的修改没有悄悄覆盖 A)

配置发布工具应该永远带上自己读到的 version 去写,这样两个管理员同时改同一份配置时,后写的人会被拒绝并被迫重新看一遍。

冲突与重试

实测 · 本机执行输出
  不带 version:最终 35 / 200(丢 165);带 version 重试:最终 200 / 200,为此重试了 763 次

重试次数很多是因为 10 个客户端在满速互相冲突(热点)。真实的配置发布冲突很少,这里是故意放大的极端情况。

历史与回滚

实测 · 本机执行输出
  /hist/app 子节点: ['ver-0000000000', 'ver-0000000001', 'ver-0000000002']
  /app 的当前 stat.version = 4;它只有当前内容:{"timeout": 5}
  回滚(把 ver-0000000001 的内容写回 /app):写入耗时 17.0 ms;50 / 50 个实例收到旧值,最晚 35.9 ms

回滚就是「把旧内容当作一次新的发布」,所以所有实例收到的是一次普通更新(见上一篇的推送延迟)。历史节点会占内存,要定期清理旧版本。

一次改多项

实测 · 本机执行输出
  两个节点(写入用 multi 原子提交)、读者分两次 get:1372 次读取里有 500 次读到 a≠b;单个 JSON 节点:1625 次读取里 0 次不一致

相关的配置项(比如主机与端口)要么放进同一个节点,要么让读者先读一个「版本指针」节点再按版本读。multi 只解决写入的原子性。

分步演示

配置版本控制
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。kazoo 2.11.0;第 4 项的写入端满速连续提交,读者满速连续读,不一致次数取决于这个压力,不是真实业务下的概率
    • 没有验证:版本指针方案(读者先读指针再读版本节点)、历史节点的自动清理、发布前的 schema 校验、权限分离(只有发布工具可写,见第 71 篇)
    京ICP备2026067502号-1京公网安备11010502063487号