从单体到微服务的演进
前言
没有哪个架构是"最好的",只有"最适合当前阶段的"。 从单体到微服务不是一步到位的跳跃,而是随着业务规模和团队规模增长,逐步演进的过程。过早拆分微服务和过晚拆分一样危险。
这篇文章会带你学什么?
学完这章后,你将获得:
- 演进路径:理解从单体到微服务的四个阶段
- 拆分时机:知道什么时候该拆、什么时候不该拆
- 拆分策略:掌握按业务域拆分的方法论
- 通信模式:了解服务间同步和异步通信的选择
- 数据拆分:理解数据库拆分的挑战和方案
| 章节 | 内容 | 核心概念 |
|---|---|---|
| 第 1 章 | 架构演进路径 | 单体→模块化→SOA→微服务 |
| 第 2 章 | 拆分时机与原则 | Conway 定律、团队自治 |
| 第 3 章 | 拆分策略 | DDD 限界上下文、绞杀者模式 |
| 第 4 章 | 服务通信 | REST、gRPC、消息队列 |
| 第 5 章 | 数据拆分 | 数据库拆分、数据同步 |
1. 架构演进路径
架构演进不是技术驱动的,而是组织规模驱动的。当团队从 5 人增长到 500 人时,单体架构的协作效率会急剧下降。
| 阶段 | 架构 | 团队规模 | 特点 |
|---|---|---|---|
| 起步期 | 单体应用 | 1~10 人 | 所有代码在一个项目中,部署简单 |
| 成长期 | 模块化单体 | 10~50 人 | 代码按模块划分,但仍然一起部署 |
| 扩张期 | SOA(面向服务) | 50~200 人 | 按业务线拆分为粗粒度服务 |
| 规模期 | 微服务 | 200+ 人 | 细粒度服务,每个团队独立开发部署 |
Conway 定律
"设计系统的组织,其产生的架构等同于组织的沟通结构。"——Melvin Conway
简单说:3 个团队做一个系统,最终会变成 3 个服务。架构拆分的本质是组织拆分。
反向 Conway 定律:既然组织结构决定了系统架构,那么想要什么样的架构,就先调整成什么样的组织结构。比如你想拆出独立的支付服务,就先组建一个独立的支付团队。很多公司微服务拆分失败,不是技术问题,而是组织没有跟着调整。
2. 什么时候该拆微服务?
不是所有系统都需要微服务。过早拆分会带来不必要的复杂性。
| 信号 | 说明 | 建议 |
|---|---|---|
| 部署冲突频繁 | 多个团队改同一个代码库,经常冲突 | 考虑拆分 |
| 某模块需要独立扩容 | 搜索模块需要 10 倍于其他模块的资源 | 考虑拆分 |
| 技术栈需要差异化 | AI 模块用 Python,主站用 Java | 考虑拆分 |
| 团队 < 10 人 | 沟通成本低,单体足够 | 不要拆 |
| 业务还在探索期 | 需求变化快,边界不清晰 | 不要拆 |
| 没有 DevOps 能力 | 没有 CI/CD、容器化、监控体系 | 不要拆 |
3. 拆分策略
3.1 按业务域拆分(DDD 限界上下文)
DDD(领域驱动设计)的限界上下文(Bounded Context)是拆分微服务的最佳指导原则。每个限界上下文对应一个独立的业务域,有自己的数据模型和业务规则。
什么是限界上下文? 同一个词在不同业务域中含义不同。比如"用户"在用户域是指注册信息(姓名、邮箱),在订单域是指下单人(收货地址、支付方式),在推荐域是指行为画像(浏览历史、偏好标签)。限界上下文就是划定一个边界,在这个边界内,术语和模型有明确统一的含义。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 用户域 │ │ 订单域 │ │ 支付域 │
│ │ │ │ │ │
│ User │ │ Order │ │ Payment │
│ Profile │ │ OrderItem │ │ Refund │
│ Address │ │ Cart │ │ Transaction │
│ │ │ │ │ │
│ 用户服务 │ │ 订单服务 │ │ 支付服务 │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────── API 调用 / 事件通信 ───────┘| 限界上下文 | 核心实体 | 对应服务 |
|---|---|---|
| 用户域 | User、Profile、Address | 用户服务 |
| 商品域 | Product、Category、SKU | 商品服务 |
| 订单域 | Order、OrderItem | 订单服务 |
| 支付域 | Payment、Refund | 支付服务 |
| 物流域 | Shipment、Tracking | 物流服务 |
3.2 绞杀者模式(Strangler Fig Pattern)
不要一次性重写整个单体,而是像绞杀榕一样,逐步用新服务替换旧模块:
- 在单体外部创建新服务
- 通过代理层将部分流量路由到新服务
- 验证新服务稳定后,逐步迁移更多流量
- 最终完全替换旧模块
4. 服务通信模式
| 方式 | 协议 | 特点 | 适用场景 |
|---|---|---|---|
| REST | HTTP/JSON | 简单通用,生态好 | 对外 API、CRUD 操作 |
| gRPC | HTTP/2 + Protobuf | 高性能,强类型 | 内部服务间高频调用 |
| 消息队列 | AMQP/Kafka | 异步解耦,削峰填谷 | 事件通知、异步任务 |
| GraphQL | HTTP/JSON | 客户端按需查询 | BFF 层、移动端 |
同步 vs 异步的选择
- 需要立即返回结果 → 同步(REST/gRPC)
- 不需要立即返回 → 异步(消息队列)
- 一个事件触发多个动作 → 异步(发布-订阅)
经验法则:能异步就异步,同步调用链越长,系统越脆弱。
5. 数据拆分:最难的部分
微服务拆分中最痛苦的不是代码拆分,而是数据库拆分。每个服务应该拥有自己的数据库,但这意味着跨服务查询变得困难。
| 挑战 | 描述 | 解决方案 |
|---|---|---|
| 跨服务 JOIN | 不能直接 JOIN 两个服务的表 | API 组合查询、数据冗余 |
| 分布式事务 | 跨库事务无法用本地事务 | Saga、本地消息表 |
| 数据一致性 | 多个服务的数据可能暂时不一致 | 最终一致性、事件驱动 |
| 数据迁移 | 从共享库迁移到独立库 | 双写过渡、数据同步工具 |
总结
从单体到微服务是一个渐进的过程,不是一蹴而就的革命。
回顾本章的关键要点:
- 演进路径:单体→模块化单体→SOA→微服务,每一步都有明确的驱动力
- 拆分时机:团队规模、部署冲突、扩容需求是拆分的信号
- 拆分策略:用 DDD 限界上下文指导拆分,用绞杀者模式渐进迁移
- 通信选择:能异步就异步,同步调用链越短越好
- 数据拆分:最难但最重要,接受最终一致性是关键心态转变
延伸阅读
- Building Microservices - Sam Newman 微服务经典
- Monolith to Microservices - 渐进式迁移指南
- Domain-Driven Design - Eric Evans 的 DDD 经典
- The Strangler Fig Pattern - Martin Fowler 的绞杀者模式
