DEV Community

架构师小白
架构师小白

Posted on

Strangler Fig 模式深度指南:渐进式遗留系统迁移的艺术

Strangler Fig 模式深度指南:渐进式遗留系统迁移的艺术

"不要一次性重写整个系统——逐步接管它。"

在软件工程的世界里,遗留系统就像一颗根系深入土壤的老树。你不可能在一夜之间连根拔起,那样只会让系统崩溃。Strangler Fig 模式(迁移 strangler) 就是来解决这个问题的:它教你如何像寄生植物一样,一步步"绞杀"旧系统,让新系统在旧系统的位置上茁壮成长。

为什么我们需要 Strangler Fig?

想象一下这些场景:

  • 你接手了一个 15 年的单体应用,技术栈老旧得找不到文档
  • 客户要求新功能,但你不敢在核心代码上冒险
  • 重写团队尝试了两次,都以失败告终——新系统永远无法完全替代旧系统
  • 你接手时发现系统已经没人敢碰了,任何改动都像在拆炸弹

这些都是真实的痛点。传统的"大爆炸"式重写(Big Bang Rewrite)失败率极高,原因很简单:

  1. 时间跨度太长——两年后,业务需求早就变了
  2. 风险太高——一次性替换所有功能几乎不可能
  3. 团队疲惫——没人能同时维护两套系统

Strangler Fig 模式的核心思想是:不要重写,而是逐步迁移。 就像一棵绞杀榕(Strangler Fig)在旧树上生长,最终完全取代它。

模式的核心机制

Strangler Fig 模式有四个关键组件:

1. 基础设施代理(Infrastructure Proxy)

在用户和旧系统之间建立一个"门神",所有请求先经过它。这可以是 API Gateway、反向代理(如 Nginx、Envoy),或者是你的业务层中的一个路由模块。

用户请求 → 代理层 → [决定走新还是走旧]
Enter fullscreen mode Exit fullscreen mode

2. 特征开关(Feature Toggle)

这是控制迁移进度的"开关"。每个要迁移的功能都有一个开关——打开时走新系统,关闭时走旧系统。这样你可以:

  • 先把流量切 1% 到新系统,观察是否有问题
  • 逐步增加比例,直到 100%
  • 如果新系统出问题,立即切回旧系统

3. 防腐层(Anti-Corruption Layer)

这是新旧系统之间的"翻译官"。新系统不需要直接理解旧系统的数据结构,而是通过防腐层来转换:

新系统 ←→ 防腐层 ←→ 旧系统
Enter fullscreen mode Exit fullscreen mode

防腐层负责:

  • 数据格式转换(JSON ↔ XML ↔ 旧格式)
  • 接口适配(REST ↔ RPC)
  • 业务规则桥接

4. 增量迁移路径(Incremental Migration Path)

这是最重要的——你要规划一条"蚕食"路线。每次只迁移一个子域、一个模块、一个功能。常见的迁移顺序:

  1. 最外围的边界服务——如通知、搜索、报表(风险低,收益明显)
  2. 独立的子域——如用户中心、订单管理(有一定复杂度但相对独立)
  3. 核心业务域——最后才迁移最关键的部分(风险最高)

实战案例:电商订单系统的迁移

让我们通过一个真实的例子来理解。假设你有一个 PHP 写的单体电商系统,现在要用 Go 重构。

步骤一:建立代理层

// nginx 配置示例
location /api/ {
    # 所有请求先经过代理层
    proxy_pass http://migration-proxy;
}
Enter fullscreen mode Exit fullscreen mode

代理层根据配置决定路由:

// 路由配置
routes := map[string]string{
    "/api/products": "new",    // 走新 Go 服务
    "/api/orders":    "old",    // 走旧 PHP 服务
    "/api/users":    "both",   // 双写,验证一致性
}
Enter fullscreen mode Exit fullscreen mode

步骤二:迁移第一个功能——产品搜索

搜索是相对独立的功能,出问题不��响核心流程。

// 新 Go 服务 - 产品搜索
func SearchProducts(q string) []Product {
    // 从新数据库查询
    return newDB.Search(q)
}
Enter fullscreen mode Exit fullscreen mode

配置路由:

{
    "feature": "product-search",
    "enabled": true,
    "target": "new",
    "fallback": "old"
}
Enter fullscreen mode Exit fullscreen mode

步骤三:双写验证

对于关键数据(如订单),你要确保新旧系统数据一致:

// 双写逻辑
func CreateOrder(order Order) {
    // 同时写入新旧系统
    oldResult := oldPHPAPI.CreateOrder(order)
    newResult := newGoAPI.CreateOrder(order)

    // 比对结果,不一致则告警
    if oldResult.ID != newResult.ID {
        alert("数据不一致!")
    }
}
Enter fullscreen mode Exit fullscreen mode

步骤四:逐步切流

// 阶段一:1% 流量到新系统
{"target": "new", "percentage": 1}

// 阶段二:观察一周后增加到 10%
{"target": "new", "percentage": 10}

// 阶段三:增加到 50%
{"target": "new", "percentage": 50}

// 阶段四:100%,旧系统下线
{"target": "new", "percentage": 100}
Enter fullscreen mode Exit fullscreen mode

模式的优势与风险

优势

优势 说明
降低风险 每次只迁移一小部分,出问题可快速回滚
持续交付 新功能可以在新系统上开发,不影响旧系统
业务连续 用户无感知,平滑过渡
团队适应 团队可以逐步学习新技术栈
可验证 每个迁移都可以验证后再继续

风险与应对

风险 应对策略
数据一致性 双写 + 定期比对 + 补偿机制
性能差异 提前做性能测试,切流后监控
旧系统成为阻碍 为旧系统设置"日落日期",强制迁移
迁移范围蔓延 严格控制每次迁移的边界

何时使用这个模式?

Strangler Fig 模式适用于以下场景:

适用

  • 遗留系统需要迁移到新架构
  • 无法停止服务进行大规模重写
  • 技术栈需要升级(Ruby → Go,Java → Kotlin)
  • 微服务化拆分单体应用

不适用

  • 旧系统已经无法维护(需要先修复核心问题)
  • 业务逻辑完全未知且无文档
  • 迁移成本超过重写成本

实施检查清单

在开始迁移之前,确保你具备:

  • [ ] 完整的旧系统 API 文档或逆向得到的接口定义
  • [ ] 可访问的测试环境和生产环境的灰度发布能力
  • [ ] 监控和告警系统,能够区分新旧系统的请求
  • [ ] 回滚机制,能够在发现问题后立即切回旧系统
  • [ ] 团队对新旧两套系统都有一定的了解

总结

Strangler Fig 模式教会我们的,不仅是一种迁移技术,更是一种工程哲学

渐进优于激进,迭代优于瀑布,可控优于冒险。

当你面对一个庞大的遗留系统时,不要想着一口吃掉它。像绞杀榕一样,找到一个切入点,慢慢生长,最终取代它。

下次当你面对"要不要重写"这个问题时,先问问自己:能不能先切 1% 的流量试试?


本文是架构模式系列的第 32 篇。如果你对这个话题感兴趣,欢迎关注并交流你的实践经验。

Top comments (0)