切换
切换把流量从 9.x 服务迁到它的 V10 服务。9.x 和 V10 从不同时写入同一份数据:它们之间没有混合版本的协议,所以切换就是一次写入停止,你要选择的是这次停止的时长。
- 离线,默认方式。 停止 9.x 的写入,导入全部数据,比对,写出快照和标签,启动 V10。它的步骤最少,可能出错的地方也最少。
- 在线,大型系统的选项,用于导入时间超出可停机窗口的系统。数据导入和跟随期间 9.x 照常服务,V10 处于待机,写入停止缩短为一次最后遍历。
迁移数据描述 migrate-data 的每个命令;本页讲围绕它们的这次切换。
离线还是在线
用演练实测的导入时间与业务能接受的停机时间对比来选择,并记录这个选择。对 9.x 示例及其视图存储(四个租户之上的 50,000 个订单和 12,500 个视图)的演练,在同一个 runner 上走了两条路径:
| 路径 | 写入停止,从 9.x 停止到 V10 提供服务 | 时间花在哪里 |
|---|---|---|
| 离线 | 243.4 s | 完整导入 98.2 s,比对 23.5 s,快照 43.8 s,标签 35.4 s,启动两个服务 36.3 s |
| 在线 | 159.5 s | 最后遍历 15.2 s,比对 24.2 s,快照 46.5 s,标签 38.0 s,恢复两个服务 29.3 s |
离线的停止随整个历史增长,在线的停止随 tail 上次读取以来 9.x 写入的数据增长,再加上两者共有的步骤。能在可停机窗口内导入完的历史,就走离线。
写入之前先盘点
为每个存储和每个流记录:
- 环境、负责人、数据库、限界上下文,以及 9.x 和 V10 的版本;
- 9.x 的存储(MongoDB 事件存储、
PrepareKey集合、补偿数据库)和 V10 的目标(日志、快照、预留、失败台账); - 每种聚合类型的流和聚合数量,以及其中有 9.x 快照的比例;
- 9.x 数据的每个写入者和读取者,以及各自怎样停止;9.x 事件总线的每个外部消费者,以及它要迁往的 V10 Kafka 事件流;
- 每个 9.x 存储的备份,以及一次经过测试的恢复;
- 迁移映射,以及检查它时所对照的 V10 Manifest。
存在未知的写入者、未知的负责人或未经测试的备份时,不要开始任何写入。
演练必须留下什么
在隔离的目标上、用生产规模的数据进行端到端演练,留下:
- 在生产映射上通过的
check; - 一次被中断后无重复地续跑的
import,以及一次什么都不写的第二次运行; - 每种差异都已解释并签字确认的
compare; - 在目标上启动的 V10 服务:对迁移来的聚合发出的命令在下一个版本追加,读取和查询回答迁移来的状态,投影已重建,停放条目已列出;
- 耗时:离线时是完整导入加上
compare、snapshot和backfill-tags,在线时是最后遍历; - 两种回退都已演练,并附上无法回退的内容清单。
不再轻易回头的那一刻
在 V10 写入之后回退,需要 V10 写入的每个流都有 9.x 的形式。根据映射和 V10 的 Manifest,列出每个没有 9.x 形式的 V10 事件类型,把清单交给决定切换的人:
- 超出映射
toRevision的事件。 V10 的类升级到了 9.x 所存储形状之外,这往往就发生在迁移本身:以修订号 1 导入的 9.xOrderCreated,V10 以修订号 2 写入。V10 创建的每个新订单都会阻断回退。 - V10 新增的事件或聚合,没有任何 9.x 名字映射到它。
生产中第一次这样的写入就结束了回退窗口。要么接受从那次写入起切换是单向的,要么在窗口关闭之前,不让这些形状和类型进入 V10 的第一个版本。演练自己的拒绝说明了这一点:V10 以修订号 2 创建了订单和视图之后,export --dry-run 拒绝了这两种类型。
离线切换
- 公布窗口,停止 9.x 的写入。 停止 9.x 服务的入口流量,排空它们的命令队列,并同时停止补偿服务。检查没有遗留的 9.x 写入者。
- 备份 9.x 存储并核实备份。
- 导入: 先
check,再import。 - 关卡:
compare只显示已签字确认的差异种类,没有无法解释的未解决的键或执行。否则就在这里停下并重启 9.x:V10 什么都没有写。 - 准备读取侧:
snapshot,以及在 ABAC 读取事件模型的地方运行backfill-tags。 - 启动 V10 服务并探测关键路径:对迁移来的聚合发一条命令、一次读取、一次查询、一个订阅、停放条目。
- 迁移 9.x 总线的外部消费者到 V10 发布的事件流。
- 把流量切到 V10,然后扩容。让 9.x 保持停止,而不是删除。
- 观察约定的信号:错误和 problem 错误码、延迟、等待、订阅延迟、停放的聚合以及消费者。回退的决定依据它们做出。
在线切换
从第一次导入到切换之前,9.x 提供服务,V10 等待:tail 跟随 9.x 的写入,每个 V10 节点都以 wow.standby=true 启动。待机的节点提供对导入所写数据的读取和查询,以 503 wow.node.standby 拒绝每条命令,不运行任何订阅、停放重试、作业或快照维护,所以 V10 所做的任何事都不会写入。它报告就绪,所以切换之前就能对照生产数据检查读取侧。
- 公布窗口,停止 9.x 的写入,与离线时相同。
- 做最后一次备份并核实。
- 最后遍历。 tail 一直跟随 9.x 直到它停止时,运行
import --final:从检查点开始的一次 tail 遍历,并向回看,以捕捉较晚才可见的提交,然后再处理一次预留和未解决的执行。当导入不完整或 tail 已经落后时,它会拒绝,什么都不写;这时最后遍历就是import --restart,时长与完整历史相当。然后运行compare、snapshot和backfill-tags。 - 关卡,与离线时相同。否则回到 9.x:V10 节点仍在待机,什么都没有写。
- 对着生产目标探测待机节点。
- 迁移外部消费者。
- 恢复每个 V10 节点: 在每个节点的内部端口上发送
POST /wow/node/resume,带上集群令牌和{"by": "<operator>", "reason": "<why>"}。节点加入它的分组,开始它的工作,然后接受命令。恢复结束了写入停止;GET /wow/node/standby显示每个节点对此的记录。 - 把流量切到 V10,然后扩容。让 9.x 保持停止。
- 观察,与离线时相同。
回退
两条路径,绝不混淆:
- V10 还没有写入: 停在关卡处、仍在待机,或者在 V10 第一次提交之前切了回来。停止 V10,把流量切回,在未被改动的存储上重启 9.x。没有需要对账的东西。
- V10 已经写入。 9.x 没有 V10 的流,所以只重启 9.x 会丢失它们。停止 V10 的写入,让 9.x 保持停止,运行
migrate-data export --dry-run。它为每个上下文输出一个结论:切回、导出 N 个流、窗口已在某个时间关闭,或者 9.x 在切换之后被写入过,这需要先由人来解决。然后export按反向读取的映射,把 V10 新增的流写回 9.x 存储,可续跑且幂等,并检查每个 9.x 聚合保存的流数量与 V10 中该类型的流数量相同,然后 9.x 再次启动。在演练中,V10 写入的 16,914 条订单命令和 16,815 条视图命令在 52.9 s 内导出并完成检查。
只有事件流能回退:V10 做出的预留、它停放的条目和它的投影视图都没有 9.x 的形式,要手工对账。9.x 的快照显示的是导出的流之前的状态,直到 9.x 再次写入该实例或重新生成快照。一旦 V10 写入过,重新部署 9.x 就不是回退。
报告
记录离线还是在线以及原因;盘点结果;演练的证据,即每个命令及其退出码和计数;已签字确认的差异种类;无法回退的类型以及对它们做出的决定;窗口的各项耗时;以及切换中实际执行的步骤。数据已导入、V10 已提供服务、消费者已迁移、回退已演练,是四个独立的事实:任何一个都不意味着下一个。