Strangler Fig 模式:迁移单体应用程序的安全方法
在现代软件工程中,遗留的单体应用程序是一个常见的挑战。随着时间的推移,一个成功的代码库变得如此庞大且相互关联,以至于简单的更改都会变得有风险,部署需要数小时,并且扩展单个功能几乎是不可能的。
当团队决定通过迁移到微服务来实现系统现代化时,他们面临一个高风险问题:我们如何在不破坏当前业务的情况下重写系统?
一种选择是“大爆炸”重写——关起门来从头开始构建新系统,并在一天之内切换所有内容。然而,这是非常危险的,并且经常会导致失败。
幸运的是,有一个更安全、更可靠的替代方案:扼杀者无花果图案。在本指南中,我们将探讨 Strangler Fig 模式是什么、它为何有效,以及如何使用清晰的图表和实际代码逐步应用它。
现实世界的类比:绞杀者无花果植物
该图案以绞杀无花果命名,这是一种原产于热带雨林的植物。
绞杀无花果种子在现有“寄主”树的上部树枝上发芽。无花果不是从地面向上生长,而是向下生长:
- 它将根部沿着寄主树的树干向下延伸,直到到达森林地面并固定在土壤中。
- 随着时间的推移,更多的根会生长出来,包裹住宿主,并融合在一起。
- 无花果长出的叶子会阻挡光线到达寄主树。
- 最终,寄主树死亡并腐烂,留下一棵空心的绞杀无花果树坚强地矗立在它的位置上。
在软件架构中,传统的单体是主机树,新的微服务是绞杀者图。我们围绕整体系统的边缘构建新服务,逐渐将流量从遗留系统中转移出来,直到整体系统可以完全关闭。
为什么“大爆炸”重写失败
在深入探讨 Strangler Fig 模式的机制之前,让我们先了解为什么替代方案(完全重写)如此危险:
- 数月(或数年)没有价值:开发人员花费很长时间编写代码,但在整个项目完成之前,所有代码都不会上线。
- 范围蔓延:在两年重写期间,业务需求发生变化。目标发生了变化,新系统必须支持重写开始时不存在的功能。
- 缺少隐式行为:单体包含多年未记录的错误修复和边缘情况处理。彻底重写常常会忘记这些细节。
- 高部署风险:如果出现问题,关闭大型遗留系统并立即打开新系统会产生巨大的爆炸半径。
扼杀者无花果图案的工作原理
Strangler Fig 模式的核心思想是增量迁移。您无需迁移整个系统,而是一次迁移一个小功能或“片段”。
迁移过程分五个关键阶段执行:
1. 识别有界上下文
查看您的整体架构并确定易于提取的单一、独立的业务功能。好的候选人包括:
- 经常更改的功能(因此团队可以从独立部署中快速受益)。
- 用于测试迁移管道的简单、低风险功能(例如静态常见问题解答或用户首选项部分)。
- 具有清晰、定义明确的数据库边界的功能。
2. 实施新的微服务
将已确定的功能构建为全新的现代微服务。该服务拥有自己的数据库、自己的部署管道,并且是使用现代技术堆栈构建的。至关重要的是,单体应用中的旧功能目前仍然有效且没有变化。
3.引入拦截层
为了使迁移对用户透明,您可以在应用程序前面引入拦截层(例如 API 网关或反向代理)。现在所有客户端流量都会首先流向此网关。
最初,网关将 100% 的所有请求路由到遗留单体。
4. 增量过渡流量
一旦新的微服务经过全面测试并准备就绪,您就可以更新拦截层中的路由规则。网关不会将迁移功能(例如 /api/users)的请求路由到整体架构,而是将它们重定向到新的微服务。
所有其他请求将继续发送至单体应用。如果新服务失败或出现错误,您可以快速更新网关以将流量路由回整体架构,从而确保将中断降至最低。
5. 退役并重复
当新的微服务稳定运行一段时间后,您可以安全地删除遗留单体中的相应代码。
然后,您选择下一个功能并重复该过程。随着时间的推移,整体系统会缩小,直到没有剩余流量,您可以完全停用旧服务器。
拦截层:快速路由示例
Strangler Fig 模式的核心是拦截层。它允许您在不修改客户端应用程序(Web 应用程序、移动应用程序)的情况下重定向请求。
下面是一个使用基于 Express 的网关代理的实用 Node.js 示例。它动态路由请求:如果传入请求与迁移的路径匹配,则它们将转到新服务;否则,他们就会退回到遗留的庞然大物。
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const PORT = 8080;
// Configuration: Target server URLs
const LEGACY_MONOLITH_URL = 'http://legacy-monolith-server:3000';
const NEW_USER_SERVICE_URL = 'http://new-user-service:3001';
const NEW_PAYMENT_SERVICE_URL = 'http://new-payment-service:3002';
// Simple logging middleware to track traffic distribution
app.use((req, res, next) => {
console.log(`[ROUTE LOG] Incoming request: ${req.method} ${req.url}`);
next();
});
// 1. MIGRATED: User registration & profile requests route to the new microservice
app.use('/api/users', createProxyMiddleware({
target: NEW_USER_SERVICE_URL,
changeOrigin: true,
pathRewrite: {
'^/api/users': '/v1/users', // Translate path format if necessary
}
}));
// 2. MIGRATED: Payment transactions route to the new payment microservice
app.use('/api/payments', createProxyMiddleware({
target: NEW_PAYMENT_SERVICE_URL,
changeOrigin: true
}));
// 3. FALLBACK: All other legacy routes automatically default to the Monolith
app.use('/', createProxyMiddleware({
target: LEGACY_MONOLITH_URL,
changeOrigin: true
}));
app.listen(PORT, () => {
console.log(`Interception Gateway routing traffic successfully on port ${PORT}`);
});
处理数据库:最难的部分
虽然路由 API 流量相对容易,但管理数据是整体迁移中最具挑战性的方面。整体架构通常有一个单一的、庞大的数据库,其中的表是高度连接的。
当您提取服务时,您还必须提取其数据。有两种常见的方法来处理这个问题:
- 双写:拦截层或应用程序在过渡阶段同时将数据写入旧数据库和新服务数据库。这使两个数据库保持同步。
- 更改数据捕获 (CDC):像 Debezium 这样的工具会监视旧数据库事务日志,并以近乎实时的方式自动将更改流式传输到新的微服务数据库。
一旦您确信数据库已完全同步并且新数据库正确无误,您就可以将读取流量切换到新服务并关闭旧表。
比较:Big Bang Rewrite 与 Strangler Fig
| 特征/指标 | 大爆炸重写 | 扼杀者无花果图案 |
|---|---|---|
| 风险级别 | 极高 | 低且受管理 |
| 反馈循环 | 非常慢(仅在最后) | 快速(连续生产测试) |
| 回滚策略 | 困难(需要恢复备份) | 简单(更改网关中的路由规则) |
| 业务影响 | 颠覆性 | 零停机 |
| 系统复杂性 | 高(构建阶段) | 高(迁移阶段) |
| 部署时间 | 海量单曲发布 | 小而频繁的更新 |
## 结论
Strangler Fig 模式是将单体迁移到微服务的行业标准。通过逐步替换遗留代码而不是一次性全部替换,您可以消除“大爆炸”故障的风险。
它可以保持部署规模较小,提供来自实际生产流量的即时反馈,并允许您的团队在整个迁移生命周期中继续提供业务价值。
虽然管理数据库迁移和运行两个并行系统增加了操作复杂性,但它提供的安全性、可预测性和稳定性使其成为现代云迁移的首选。