Skip to content

从单体到微服务:演进与拆分原则

本文是微服务架构系统学习系列的 L1 入门篇。前置:建议先读分布式系列 26. from-monolith-to-distributed-cap-base。 学完可以配合面试题食用:01-soa-vs-microservice16-monolith-to-microservices-seven-steps20-microservice-problem-450-vs-6-modules

单体不丢人:先别焦虑

2025 年的技术社群,一说"还在用单体"就矮半截。但事实是:单体是中小团队的最优解,不是技术债。

一个 10 人团队,一个 Spring Boot 单体服务,每天 deploy 10 次,迭代速度比拆成 20 个微服务快得多。为什么?因为微服务每个部署都要走 CI/CD、配置中心、服务发现、可观测性,这一套基础设施选型+搭建至少 2-3 周,而单体改一行代码就 mvn package && scp 上去了。

有个反直觉的规律:团队规模 < 10 人时,单体比微服务快;10-15 人时,模块化单体够用;超过 15 人,才轮到微服务上场。

// 一个简单的单体支付模块,一行代码搞定事务
@Transactional
public void pay(Order order, Account account) {
    orderRepo.save(order);
    accountRepo.deduct(account, order.getAmount());
}

换成微服务,这段代码变成:订单服务调账户服务调用余额服务,每步都得处理网络超时、幂等、分布式事务。成本翻 5 倍,产出是零。

什么时候该拆:三个信号

拆服务不是未雨绸缪,是被痛点逼的。出现以下三个信号之一,再考虑拆:

信号一:部署互相阻塞。 订单组改了数据库表结构,通知组得等半天才能上线。两个功能的发版周期从 1 天变成 1 周。这时候"一起部署"不再是优势,而是瓶颈。

信号二:故障爆炸半径太大。 一个内存泄漏的 OOM 异常,把整个应用拖垮,所有接口 502。用户模块挂了,连带支付、搜索、通知全停。单体里没有故障隔离,一个 bug 等于全站挂。

信号三:团队规模触到康威定律临界点。 康威定律说:"设计系统的架构受制于产生这些设计的组织的沟通结构。" 翻译成人话:两个团队协作维护同一个代码库,接口文档和代码合并就是每天的战场。 15 人以上的团队还在一个单体仓库里开发,每个 PR 的冲突概率和 code review 的等待时间都会指数级增长。

拆分原则:按业务能力,不是按技术层

很多团队第一步就错了:按技术层拆——前端一个服务、后端一个服务、数据库一个服务。这不叫微服务,这叫把单体垂直切了三刀。

正确做法是按业务能力(DDD 的限界上下文)拆分:

yaml
# 按技术层拆(错误)
services:
  - controller-service   # 所有 Controller 放一起
  - service-service      # 所有 Service 放一起
  - dao-service          # 所有 DAO 放一起

# 按业务能力拆(正确)
services:
  - order-service        # 订单上下文:订单 + 订单项 + 支付记录
  - user-service         # 用户上下文:用户 + 地址 + 积分
  - payment-service      # 支付上下文:交易 + 退款 + 对账

按业务能力拆有几个硬约束:

  • 数据库必须跟着拆。每个服务拥有自己的数据库,其他服务只能通过 API 访问。两个服务读写同一张表,等于没拆。
  • 服务间只通过接口通信,不共享 Domain 对象。order-service 的 Order 对象和 payment-service 的 OrderDTO 是两个东西,不要用同一个类。
  • 每个服务自治:自己能独立部署、独立升版、独立扩缩容。

拆分路径:绞杀者模式是正解

不要搞"big bang 重写"——那个死在 archive 文件夹里的项目叫 v2。正确路径是绞杀者模式(Strangler Fig Pattern)

┌─────────────── 单体应用 ───────────────┐
│  ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│  │ 订单模块 │ │ 用户模块 │ │ 支付模块  │ │
│  └────┬────┘ └────┬────┘ └─────┬────┘ │
└───────┼───────────┼─────────────┼──────┘
        │           │             │
        │   第一步   │             │
        │   模块化   │             │
        ▼           ▼             ▼
┌───────────────  单体应用  ───────────────┐
│  ┌──────────────────────────────────────┐│
│  │  订单模块 (清晰包边界+接口)          ││
│  │  用户模块 (清晰包边界+接口)          ││
│  │  支付模块 (清晰包边界+接口)          ││
│  └──────────────────────────────────────┘│
└──────────────────────────────────────────┘
        │           │             │
        │   第二步   │             │
        │   抽独立服务 │             │
        ▼           ▼             ▼
┌─────────┐  ┌─────────┐  ┌──────────┐
│ 订单服务  │  │ 用户服务  │  │ 支付服务   │
│ (独立进程) │  │ (独立进程) │  │ (独立进程)  │
└─────────┘  └─────────┘  └──────────┘

第一步:模块化单体。 不拆进程,先拆代码结构。把单体应用内的包结构按业务边界重新组织,每个模块只通过接口暴露能力,内部实现完全隔离。这一步不需要任何基础设施投入,成本最低。

第二步:逐个抽离。 选一个边界最清晰、依赖最少的模块(通常是用户模块),先抽成独立服务。老单体通过 RPC 调用新服务,新服务通过 API 查询老单体——中间状态可以共存几个月。这就是绞杀者模式的核心:不一次推倒,一片一片替换。

下面是一个模块化单体到微服务的目录结构对比:

# 单体(混乱版)
src/main/java/com/example/
├── controller/        # 所有 Controller
├── service/           # 所有 Service
├── dao/               # 所有 DAO
├── model/             # 所有 Entity
└── Application.java

# 模块化单体(清晰版)
src/main/java/com/example/
├── order/
│   ├── OrderController.java
│   ├── OrderService.java     # interface
│   ├── OrderServiceImpl.java # package-private
│   └── OrderRepository.java
├── user/
│   ├── UserController.java
│   ├── UserService.java
│   ├── UserServiceImpl.java
│   └── UserRepository.java
└── payment/
    ├── PaymentController.java
    ├── PaymentService.java
    ├── PaymentServiceImpl.java
    └── PaymentRepository.java

微服务的税单:算完账再拆

微服务不是免费的升级。拆之前,把这些"税"算清楚:

税项单体微服务
网络调用方法调用,≈0msRPC,≈1-10ms,还有超时/重试
事务@Transactional 搞定分布式事务(TCC/Saga),至少 10 倍复杂度
可观测性一个日志文件链路追踪 + 指标 + 结构化日志,一套基础设施
部署scp 一个 jarCI/CD + 容器编排 + 配置中心 + 服务发现
测试本地跑完整契约测试 + 集成测试 + 端到端测试

一个数量级参考: 拆成 10 个微服务,运维成本大约是单体的 5-8 倍。如果业务复杂度不够,这些成本就是纯浪费。

常见误区与小结

常见误区:

  • "微服务 == 性能好":错了。微服务多了网络延迟,性能通常比单体差。微服务解决的是组织效率问题,不是性能。
  • "先拆了再说,后面再重构":拆之前没有清晰的业务边界,拆完就是 10 个更难维护的单体,每个都耦合。
  • "每个服务都用不同语言":多语言微服务增加了运维成本和团队协作成本。15 人团队推荐统一语言,除非有硬性理由(如性能敏感模块用 Go)。
  • "微服务解决了所有问题":微服务解决的是部署耦合和团队协作问题,但引入了分布式事务、网络不可靠、运维复杂度。没有银弹。
  • "拆完就完了":微服务架构需要持续治理——服务版本管理、接口兼容性、依赖关系图,这些是长期维护工作。

小结:

单体是起点,模块化单体是过渡,微服务是痛到不得不做的选择。本文讲清楚了"什么时候拆"和"怎么拆"——下一个核心问题是拆完之后,服务之间怎么互相找到(注册与发现)和怎么管理配置,即 28. registry-config-center

参考

参考:Martin Fowler, "Microservices" (2014) — 微服务定义原文;Sam Newman, "Building Microservices" 第 1-3 章;Eric Evans, "Domain-Driven Design" 限界上下文章节。

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1