配置能热更新之后,下一批问题是:两个人同时改怎么办、改错了怎么回滚、一次改多项如何不让应用看到中间状态。这篇用 ZooKeeper 的 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 只解决写入的原子性。