13.2 风险驱动的服务提取与 Strangler 迁移
服务拆分不是把目录复制到新仓库,而是改变部署、数据、故障和团队责任的边界。迁移成功的标志也不是“服务数量增加”,而是目标约束得到改善,并且新增复杂度可被运营。
先写出拆分假设
一个可验证的拆分理由应包含现状、目标和度量。下面数字是假设案例,用于说明决策写法:
计分模块占峰值 CPU 的 70%,但必须随低负载的报名模块一起扩容。把计分提取为独立服务后,希望基础设施成本下降 25%,同时结算成功率不低于 99.95%。
这还不是结论。CPU 占比不能直接推出可节省费用,应在可比流量和时间窗口下测量实例、网络、存储与新增运维成本;成功率按逻辑结算请求统计,超时/未知结果要纳入,不能用多次重试把分母做大。
常见有效驱动包括:
- 独立扩缩能显著降低成本或资源争用;
- 发布节奏差异造成真实等待;
- 故障隔离有明确业务价值;
- 合规或数据驻留要求形成硬边界;
- 团队可以端到端拥有构建、部署和运行。
“微服务更先进”“代码很多”都不是足够的决策依据。
选择第一条切缝
优先选择业务边界清楚、数据所有权明确、调用扇入可控的能力。第一步应小到能验证平台能力,又大到能产生独立价值。
拆分前至少盘点:
- 上下游调用者和协议;
- 读写的表、文件与缓存;
- 事务和一致性要求;
- 峰值流量、延迟预算和失败语义;
- 值班、告警、容量和恢复责任。
Strangler:逐步替换流量
Strangler Fig 迁移在旧系统前建立路由边界,让新旧实现并存:
客户端
│
▼
Gateway / Facade
├── /scores/query ──> 新计分服务
├── /scores/write ──> 旧单体
└── 其他请求 ──> 旧单体此时新读路径必须有来自旧写入端的数据同步,并声明新鲜度;路由器本身也要监控容量和失败。分流最好按稳定的租户或用户队列进行,避免同一会话在两个不等价的数据视图间跳来跳去。影子读取不得触发真实扣款、通知等副作用。
一次迁移通常包含:
- 为旧能力建立可观测的契约边界;
- 新服务实现同一契约并做影子读取或离线对比;
- 按租户、用户或流量比例切读请求;
- 迁移写入所有权;
- 停止旧路径,观察一个完整业务周期;
- 删除旧代码和兼容设施。
最后一步不可省略。永久双轨会让临时迁移层成为下一代遗留系统。
写请求不能盲目回退
下面的策略很危险:新服务超时后自动调用旧单体。超时只表示客户端没有收到结果,不表示新服务没有提交;回退可能造成重复扣款或重复结算。
写路径需要:
- 稳定的幂等键;
- 可查询的操作状态;
- 明确区分“确定失败”和“结果未知”;
- 重试预算与退避;
- 对账与人工修复路径。
读请求在陈旧数据可接受时更容易回退,但仍应记录来源和一致性差异。新服务接管写入后,旧读路径若没有同步这些写入,就不能承诺“读到自己刚提交的结果”;恢复旧路由不等于数据也跟着倒车。幂等键也要由新旧路径共享判定或稳定路由到唯一写入所有者,两边各自去重不能阻止跨边界重复。
迁移数据所有权
直接双写两个数据库会产生部分成功。更可靠的选择取决于阶段:
历史回填 + 变更捕获
快照与增量必须在同一日志位置衔接:由连接器协调一致性快照和变更起点,在回填期间保留所需日志并处理增量,不能先扫完表再临时开启 CDC,否则中间的更新会漏掉。更新、删除、重复事件和重启续传都要纳入验证;具体流程取决于数据库和连接器。
切换前按业务主键、计数、内容校验和及关键不变式对账,并检查复制延迟。接管写入时,先对切流范围阻止旧写入,等在途事务结束并记录最终位点,确认新端追上该位点,再只开放新端写入。若不能短暂停写,就需要等价的写入栅栏与所有权交接协议。
Transactional Outbox
旧系统在同一数据库事务中写业务数据和 Outbox,由独立发布器把变更交给新服务。它把待发送意图与业务提交一起保存,消除“已提交却丢失发送意图”的缺口;提交到发布之间仍有延迟。发布器需恢复重试,崩溃可能导致重复投递,消费者仍要幂等并按业务处理顺序。
Expand and Contract
协议或 schema 先扩展到新旧版本都能接受,迁移生产者和消费者后,再删除旧字段。破坏性变更不能与流量切换绑在同一个不可逆步骤。
数据所有权完成迁移后,旧系统不应继续把新服务数据库当共享表访问。
把故障模型当成设计输入
进程内调用变成网络调用后,会新增:
- 连接失败、超时和部分响应;
- 重试放大与排队;
- 版本并存;
- 调用链追踪和跨服务鉴权;
- 服务正常但依赖退化的灰色故障。
每个远程调用都应有超时,但不是每个失败都应重试。只有幂等或带幂等保护的操作适合自动重试;重试还要有次数、总时长和抖动,避免同步风暴。
每一步都保留退出条件
迁移计划至少写明:
| 项目 | 示例 |
|---|---|
| 成功指标 | p95 延迟、成功率、成本、发布等待时间 |
| 不变量 | 积分增量符合规则、每场比赛只产生一次有效结算 |
| 切流单位 | 内部账号 → 5% 用户 → 25% → 100% |
| 停止条件 | 错误率高于基线 0.2 个百分点 |
| 回退方式 | 路由恢复旧读路径;写路径暂停并对账 |
| 最终清理 | 删除旧表写入、旧 API 和迁移开关 |
停止阈值也要定义统计窗口和最少样本数。例如基线错误率为 0.1%,增加 0.2 个百分点后的阈值是 0.3%,不是 0.1002%。读路径回退还需验证旧端新鲜度;写入接管后,回退必须先重新交接写入权并追平数据,不能只拨回开关。
完成迁移后重新评估最初假设。如果独立服务没有改善目标指标,应允许合并或停止继续拆分,而不是用沉没成本推动更多服务。
下一章讨论服务边界已经存在之后的基础通信模式:发现、网关、超时、重试和断路器如何组合成可预测的调用链。
参考资料
Martin Fowler, Strangler Fig Application
AWS Prescriptive Guidance, Strangler fig pattern
Chris Richardson, Transactional Outbox