绞杀者模式(Strangler Fig)

深入绞杀者(Strangler Fig)模式:遗留系统渐进改造的思路,Facade 拦截与路由分流,按功能与按路由渐进替换的策略,数据双写与迁移,以及新旧系统切换与旧系统拆除的完整路径。

老系统不是用来"推倒重来"的,而是用来"慢慢绞死"的。绞杀者模式(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 控制入口、按功能与路由渐进替换、靠双写与对账搬数据,最后在新系统站稳后从容拆除旧系统,整个旅程永远有活路、永远能回退。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「架构」更多文章

  1. 功能开关与发布工程
  2. 边车模式(Sidecar)
  3. 限界上下文策略