老系统不是用来"推倒重来"的,而是用来"慢慢绞死"的。绞杀者模式(Strangler Fig)借鉴榕树绞杀宿主树的自然现象:让新系统沿着旧系统外围生长,一段一段接管功能,直到旧系统被完全替换。它避开"大爆炸式重写"的风险,把遗留改造变成可持续交付的渐进过程。本文覆盖 Facade 拦截、按功能/路由替换、数据迁移与拆除全流程。
1. 为什么不能大爆炸重写
1.1 重写的困境
遗留系统往往运行了多年,积累了隐藏业务规则、边界情况与历史包袱。全量重写意味着一次交付巨大变更,风险集中、进度失控、业务规则极易在重写中丢失。
1.2 绞杀者的思路
不动旧系统、从外围逐步替代:先建新壳(Facade),再一段段替换功能,最后拆掉旧系统。任何时刻都只有一个系统在服务,业务连续性不断。
阶段一:外面套壳 阶段二:逐个替换 阶段三:旧壳消亡
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 新系统 Facade │ │ 新A 新B │ │ 新A 新B 新C │
│ (入口统一) │ │ 旧X 旧Y │ │ │
└─────────────┘ └─────────────┘ └─────────────┘
| 方式 | 风险 | 交付 | 适用 |
|---|---|---|---|
| 大爆炸重写 | 极高 | 一次大版本 | 极少数小系统 |
| 绞杀者渐进 | 可控 | 持续小步 | 绝大多数遗留系统 |
| 双轨并行 | 中 | 并行双写 | 有强一致诉求的场景 |
一句话:绞杀者模式 = “围着老系统种一圈新系统,一截一截替换”——永远有一条活路在跑,重写的风险被摊到无数小步里。
2. Facade 拦截统一入口
2.1 什么是 Facade
在新旧系统之间放一个门面(Facade),所有请求先经过它,由它决定路由到旧系统还是新系统:
客户端 ──→ Facade(统一入口)
├──→ 旧系统(默认全部)
└──→ 新系统(按功能/路由渐进切换)
2.2 Facade 的职责
| 职责 | 说明 |
|---|---|
| 统一入口 | 屏蔽新旧系统的接口差异 |
| 路由决策 | 按规则把请求分给新或旧 |
| 协议适配 | 新旧协议互转 |
| 灰度开关 | 按用户/比例切换新系统 |
2.3 实现示例
// Facade:按功能模块路由
public Response handle(Request req) {
if (featureRouter.useNew(req.getModule(), req.getUserId())) {
return newSystem.handle(req); // 走新系统
}
return legacySystem.handle(req); // 走旧系统
}
一句话:Facade 是绞杀者的外科手术入口——请求先经它,路由规则即"切到哪里了"的可视化开关。
3. 按功能渐进替换
3.1 功能替换策略
按业务能力一块一块替换:先把订单查询迁到新系统,再迁下单,再迁支付……每块独立上线、独立验证。
功能替换顺序(按风险从低到高):
① 只读查询(低风险)→ ② 非核心写(中风险)→ ③ 核心写(高风险)
每个功能替换 = Facade 中加一条路由规则
3.2 替换节奏
| 功能 | 新系统能力 | 验证 | 切换 |
|---|---|---|---|
| 查询订单 | 新查询服务 | 结果对比 | 灰度 10%→100% |
| 创建订单 | 新写服务 | 双跑对比 | 白名单→全量 |
| 支付回调 | 新回调处理 | 对账一致 | 逐步放量 |
3.3 为什么按功能比按模块稳
功能是用户可感知的切片,替换后可以立刻验证业务是否正常;按"模块"替换容易把一条业务链劈成两半,新旧各管一半,对账与排障都困难。
3.4 功能替换的常见顺序
按"只读 → 非核心写 → 核心写"的顺序,能最大化早期反馈、最小化早期风险:
第一波(只读):查询订单、商品详情、用户信息 → 结果可与旧系统比对
第二波(非核心写):收藏、评价、优惠券领取 → 允许小范围试错
第三波(核心写):下单、支付、退款 → 全量前做充分双跑
- 只读替换后新旧结果比对自动化,快速暴露规则差异;
- 核心写替换前务必影子双跑 + 对账,再切真实流量。
3.5 替换完成的标准
一个功能切片"替换完成"不是路由切到 100%,而是:
| 标准 | 说明 |
|---|---|
| 功能覆盖 | 新系统行为与旧系统完全等价(含边界) |
| 数据一致 | 该功能涉及的数据对账差异为 0 |
| 稳定性 | 连续 N 个发布周期无回归 |
| 可回退 | 回退预案仍保留在 Facade 中 |
一句话:按功能替换 = “把一条条业务链完整地交给新系统”——查询先行、写操作跟上,每块都是可独立验证的小版本,切到 100% 不算完,数据与稳定达标才算。
4. 按路由渐进替换
4.1 路由替换策略
当新旧系统接口风格接近时,可以在 Facade 层按路由规则切换:按 URL 前缀、请求头、用户分桶、地域等维度,把流量逐步导向新系统。
网关/Facade 路由规则:
/api/v2/** → 新系统(全量)
/api/legacy/** → 旧系统(保留)
/api/orders/** → 新系统(已迁移)
/api/payment/** → 旧系统(未迁移)
4.2 路由替换的三层推进
| 层次 | 做法 |
|---|---|
| 影子流量 | 新系统旁路接收副本,只比对不出错 |
| 灰度流量 | 小比例真实流量进入新系统 |
| 全量切换 | 路由规则全部指向新系统 |
4.3 路由与功能的配合
路由负责"流量怎么分",功能负责"替换到什么程度"。先功能完备,再路由切量——路由切了但功能缺失,用户直接踩空。
4.4 路由规则示例
# Facade/网关路由规则:按模块与用户比例切量
routes:
- path: /api/v2/** # 新系统接口
target: new-system
weight: 100
- path: /api/orders/** # 已迁移模块
target: new-system
rules:
- bucket: user_id % 100 < 10 # 按用户 hash 灰度 10%
- path: /api/legacy/** # 未迁移模块
target: legacy-system
灰度推进示例:
orders 模块:影子 → 10% → 50% → 100%
payment 模块:影子 → 5%(白名单)→ 50% → 100%
一句话:按路由替换 = “用路由规则表达替换进度”——影子、灰度、全量三层推进,配合功能完备度,让切量永远有退路;路由规则即进度条,随时可回拨。
5. 数据迁移
5.1 数据迁移的三种方式
| 方式 | 做法 | 适用 |
|---|---|---|
| 停机迁移 | 停服复制,再切 | 小数据量、可接受停机 |
| 双写迁移 | 新旧同时写,历史一次性搬迁 | 中等规模、业务不停 |
| 同步双写 + 校验 | 双向同步加对账 | 大规模、强一致诉求 |
5.2 双写迁移流程
① 启动双写:业务同时写新库与旧库
② 历史搬迁:存量数据批量灌入新库
③ 校验对账:新旧数据比对,修正偏差
④ 停止旧写:业务只写新库,旧库只读
⑤ 旧库下线:确认一致后回收
-- 搬迁示例:分批拉取旧库数据灌入新库
INSERT INTO new_orders (id, status, amount, created_at)
SELECT id, status, amount, created_at FROM legacy_orders
WHERE id > :last_id
ORDER BY id
LIMIT 1000;
5.3 迁移的坑
- 历史数据脏数据多,要清洗与默认值兜底;
- 时间字段、ID 生成策略不一致,要映射对齐;
- 迁移期间新旧写并发,要有最终对账任务兜底。
一句话:数据迁移 = “双写过渡 + 存量搬迁 + 对账校验 + 断旧回收”——数据是最后一个被替换的部分,也是最不能出错的部分。
6. 切换与拆除
6.1 切换判定条件
不能凭感觉切,要有可度量的验收门禁:
| 维度 | 达标条件 |
|---|---|
| 功能覆盖 | 新系统功能覆盖旧系统全部 |
| 数据一致 | 对账差异率为 0 |
| 稳定性 | 新系统错误率/延迟优于旧系统 |
| 性能 | 满足 SLA 目标 |
| 兼容 | 依赖方接口契约全部对齐 |
6.2 切换与回退
切换 = 路由规则 100% 指向新系统
回退 = 路由规则改回旧系统(Facade 保留一段时间)
新系统稳定运行 N 个发布周期后,才进入拆除
6.3 拆除旧系统
- 先摘掉流量(路由已全新),观察无回退;
- 再停旧服务,保留只读备份窗口;
- 最后回收资源(代码、数据库、域名、监控);
- 拆除不是一次动作,而是一个周期的观察 + 分批下线。
一句话:切换与拆除 = “先满足门禁,再全量切,最后观察拆除”——Facade 是回退的保险,旧系统要多留一个观察期,别急着剁手。
7. 组织与风险控制
7.1 小步快跑的组织方式
- 按功能切片排迭代,每个切片独立交付与验收;
- 专人维护替换地图(功能/数据/路由迁移进度);
- 每个切片有明确负责人与回退预案。
7.2 风险清单
| 风险 | 应对 |
|---|---|
| 替换链太长 | 切分更小功能片,频繁交付 |
| 数据不一致 | 对账任务 + 差异修复流程 |
| 依赖方不配合 | 提前契约对齐 + 兼容版本 |
| 团队疲劳 | 里程碑庆祝 + 进度可视化 |
| 规则丢失 | 影子流量先跑 + 业务专家参与 |
7.3 什么时候不该用绞杀者
- 新系统是完全不同商业模式(不是演进而是替换赛道);
- 旧系统无法再提供服务(已停止维护、安全漏洞不可修);
- 只是局部重构(用不上全局绞杀,单服务内部重构即可)。
一句话:绞杀者是组织与技术并行的长期工程——小步切片、可视化进度、每条链都有回退预案,才能在大规模改造中维持节奏与士气。
8. 踩坑清单
| 坑 | 现象 | 对策 |
|---|---|---|
| 大爆炸重写 | 风险集中、周期失控 | 改用绞杀者渐进 |
| 无 Facade | 新旧接口混乱 | 先建统一门面 |
| 路由切了功能没备 | 用户踩空 | 功能完备后再切量 |
| 数据一次性迁移 | 脏数据爆发 | 双写 + 分批 + 对账 |
| 旧系统过早拆除 | 发现问题无法回退 | 保留观察期 |
| 替换顺序先难后易 | 卡在核心链 | 从只读低风险开始 |
| 无替换地图 | 进度失控重复返工 | 维护功能/数据/路由地图 |
| 忽略业务规则 | 新系统丢规则 | 影子流量 + 专家评审 |
9. 总结
| 维度 | 结论 |
|---|---|
| 核心理念 | 渐进替换而非推倒重来 |
| 入口 | Facade 统一拦截与路由 |
| 替换单元 | 按功能链、按路由切量 |
| 数据 | 双写 + 搬迁 + 对账 |
| 切换 | 门禁达标再全量,留回退 |
| 拆除 | 观察期后分批下线 |
一句话记住:绞杀者模式让遗留改造像"切香肠"一样小步进行——用 Facade 控制入口、按功能与路由渐进替换、靠双写与对账搬数据,最后在新系统站稳后从容拆除旧系统,整个旅程永远有活路、永远能回退。
延伸阅读
- 演进式架构 — 渐进式改造的理论支撑
- 单体与微服务取舍 — 拆分目标与边界
- 模块化单体 — 单体内部先切模块再演进
- 分布式数据一致性 — 双写迁移的一致性保障
- API 网关与 BFF — Facade 拦截的网关实现
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。