工程实践 · 2023年6月19日 · 3 分钟

从单体到微服务:一个电商系统的演进之路

电商后台从单体到微服务的完整演进过程:如何切分、分布式事务、服务调用与数据一致性。

去年参与了一个电商后台系统的重构,把跑了几年的单体应用拆成了微服务。这篇文章不是教程,而是记录其中的决策过程和踩过的坑。

起点:一个"还能跑"的单体

重构前的系统大概是这个样子:一个 Spring Boot 大项目,所有模块在一个代码仓库、一个进程里。订单、商品、用户、支付、物流,几十万行代码。

好处是部署简单,一个 war 包搞定。坏处也明显:发版要全员协调、代码冲突频繁、一个不起眼的修改可能拖垮整个服务——因为所有模块共享同一个 JVM。

业务量增长后,真正促使决策的是两个触发器:

  1. 大促期间数据库 CPU 顶到 100%,因为报表查询和订单写入争用同一库
  2. 一个实习生引入的第三方库有内存泄漏,拖着整个应用一起 OOM

怎么拆:边界比技术重要

最难的其实不是选什么框架(Spring Cloud / Dubbo),而是怎么切。切错了比不切还糟——远程调用性能下降、事务跨服务协调复杂、排查问题要翻好几个服务的日志。

我们的拆法按业务领域来:

┌─────────────────────────────────────────────┐
│                  API Gateway                 │
│               (路由、限流、鉴权)              │
└─────┬──────┬──────┬──────┬──────┬──────────┘
      │      │      │      │      │
  ┌───┴──┐ ┌┴────┐ ┌┴────┐ ┌┴────┐ ┌┴────────┐
  │ 用户  │ │商品 │ │订单  │ │支付  │ │ 物流     │
  │ 服务  │ │服务 │ │服务  │ │服务  │ │ 服务     │
  └──────┘ └─────┘ └─────┘ └─────┘ └─────────┘
      │      │      │      │      │
      └──────┴──────┴──────┴──────┘
                     │
              ┌──────┴──────┐
              │   消息队列   │
              │  (异步解耦)  │
              └─────────────┘

拆的过程逐步进行,先拆业务逻辑最独立的用户和商品模块,稳定后再拆订单和支付。整个过程持续了两个月,期间新旧两套系统并行。

三个现实问题

分布式事务——这是最大痛点。订单创建涉及库存扣减、支付、积分,原来一个 @Transactional 搞定,现在跨三个服务。最终选的是本地消息表 + 定时补偿的方案,不引入 Seata 之类的框架——团队没精力学。

服务间调用——一拆开链路变长,一个下单请求经过网关→订单→商品→用户→积分,网络开销翻了好几倍。排查问题从翻一个日志文件变成翻五个服务的日志。必须上链路追踪(最后选了 SkyWalking,对代码侵入最小)。

数据一致性——远程调用总有超时的时候,重试就可能重复。接口设计要支持幂等——调用方传一个唯一的 transaction_id,被调用方根据 id 去重,保证"不管调多少次,效果等于调一次"。

值不值

坦白说,拆完前三个月的维护成本是上升的。以前一个 Bug 在单体里改一行代码,现在要在两个服务间配消息、写补偿逻辑、注意幂等。

但长期收益是有的——团队可以独立发版了(不用挤在同一时间窗口)、出问题爆炸半径变小了(支付挂了不影响用户登录)、技术栈也可以按场景差异化。

架构演进的规律是:**复杂度不会消失,只会转移。**单体把复杂度集中在代码里,微服务把复杂度扩散到网络、运维和团队协作中。你选择接受哪种复杂度。

继续阅读

评论