半年前,我接手了一个帮某中型连锁零售品牌重构后端系统的活儿。他们的老系统是个典型的“大泥球”单体应用,Java 8 时代写的,代码量堆到了 120 万行。业务涨得快,但系统慢得像蜗牛,双十一这种高峰期直接宕机了三次。老板急了,让我帮忙规划怎么把这套祖传代码拆成微服务,还不能影响业务运行。说白了,就是要在不停车的情况下,把发动机换掉。
说实话,当时团队里有人建议直接推倒重来,写一套全新的微服务代码库。我反对了。120 万行代码里藏着无数的业务逻辑和边界条件,重写等于自杀。我们定下的基调是“绞杀者模式”(Strangler Pattern),像藤蔓一样慢慢包裹、替换原来的功能模块,最后让单体自然死亡。这个过程持续了 8 个月,踩了无数坑,但系统确实平滑过渡了。
第一阶段:切分边界与 API 网关
最难的不是拆代码,而是决定“在哪拆”。
起初我想按表结构拆,发现根本行不通。因为单体的事务是跨表的,拆了服务就得处理分布式事务,复杂度爆炸。后来试了一圈发现,还是得按 DDD(领域驱动设计)的业务模块切。我把系统分成了用户、订单、库存、支付四个核心域,外加三个支撑域。
这里有个关键的实战决策:我们选 API 网关而不是直接暴露服务接口。当时团队里有争议,方案 A 是前端直接调各个微服务的 HTTP 接口,方案 B 是引入 Kong 做统一网关。我选了 B。为什么?因为老系统有很多遗留的鉴权逻辑写在了 Controller 层,直接拆出去会导致每个微服务都要重复实现一遍鉴权,维护成本太高。用 Kong 统一拦截,把鉴权逻辑剥离到网关层,下面的服务只管业务逻辑,干净多了。
```yaml
kong.conf 核心配置片段
nginx_http_conf = {
"proxy_read_timeout 30s;",
"log_format main $remote_addr - $request $status;"
}
```
部署网关这一步本身不复杂,但有个大坑。我最初以为配置好上游服务地址就能跑了,结果测试环境一跑,微服务 A 调用微服务 B 居然超时了。排查了半天,发现是 Docker 容器网络模式下,服务发现机制没跟上。Kong 拿到的是静态 IP,而微服务是动态发布的。最后我们引入了 Nacos 作为注册中心,让 Kong 动态订阅服务实例,问题才解决。那个晚上我在机房盯监控日志到凌晨两点,头发都白了几根。
第二阶段:数据拆分与同步
架构拆了,数据还得跟着拆。单体的时候大家共用一个 MySQL 库,拆服务后,物理库必须分开,否则微服务就没了隔离性。
我采用的是“共享库-分表-独立库”的渐进策略。先不动数据,只改代码里的数据访问层,把 SQL 语句按领域划分。然后再做逻辑上的分离,最后才是物理库的迁移。
这里有个自我质疑的点。当时我觉得双写(一个请求同时写单体库和新微服务库)是最安全的,能保证数据一致性。结果上线第一天就翻了车。
原来,两个写操作的耗时不同。单体库是主从架构,写入稍微慢一点;微服务库是新装的,速度快。一旦发生网络抖动,就会出现“单体库写了,微服务库没写”或者反过来。更糟的是,重试机制会触发重复写入,导致订单号重复。坑死了。
后来我放弃了双写,改用 Canal 监听单体库的 Binlog 变更,单向同步到微服务库。虽然实时性稍微差那么几百毫秒,但对于非交易类的查询数据,完全够用。对于交易类数据,我们保留了单点写入,只在新旧系统并行运行期间,通过事务消息保证最终一致性。
```java
// 使用 Spring Cloud Stream + RabbitMQ 实现延迟消息补偿
@Bean
public Binding> output(MessageChannel channel) {
return new Binding<>("orders", channel);
}
```
有意思的是,在迁移库存模块时,我们发现老系统里有个隐藏极深的 Bug:高并发下库存扣减没有加锁,靠的是数据库行锁和重试。拆成微服务后,我们引入了 Redis 预扣减。这步其实不是架构要求的,而是为了性能。原来单体扛 5k QPS 都抖,拆出去加上 Redis 后,轻松到了 15k QPS。这是这次重构带来的额外红利。
收尾与反思
8 个月后,单体应用彻底下线,120 万行代码被拆成了 14 个微服务,每个服务代码量控制在 10 万行以内。部署时间从原来的 40 分钟缩短到了 5 分钟,因为是容器化部署,CI/CD 流水线一天能发十几个版本。
但我并不认为这就是完美的终局。微服务带来了分布式调用的复杂度,网络抖动、服务降级、熔断限流,这些问题在单体时代是不存在的。我们为此写了大量的测试用例,甚至专门建了一个混沌工程实验室,定期故意杀容器,看系统能不能自愈。
如果你也在做类似的迁移,记住一点:不要追求一步到位。先把最痛的那个模块拆出去,让团队体验一下微服务的好处(比如独立部署、快速迭代),其他人才会有信心跟进。架构演进不是数学证明题,没有标准答案,只有最适合当下团队能力和业务节奏的方案。
本文基于实际项目经验整理,欢迎在评论区交流技术问题。