Files
test-repo/docs/zh-cn/appendix/6-architecture-and-system-design/monolith-to-microservices.md
T

157 lines
7.4 KiB
Markdown
Raw Normal View History

# 从单体到微服务的演进
::: tip 前言
**没有哪个架构是"最好的",只有"最适合当前阶段的"。** 从单体到微服务不是一步到位的跳跃,而是随着业务规模和团队规模增长,逐步演进的过程。过早拆分微服务和过晚拆分一样危险。
:::
**这篇文章会带你学什么?**
学完这章后,你将获得:
- **演进路径**:理解从单体到微服务的四个阶段
- **拆分时机**:知道什么时候该拆、什么时候不该拆
- **拆分策略**:掌握按业务域拆分的方法论
- **通信模式**:了解服务间同步和异步通信的选择
- **数据拆分**:理解数据库拆分的挑战和方案
| 章节 | 内容 | 核心概念 |
|-----|------|---------|
| **第 1 章** | 架构演进路径 | 单体→模块化→SOA→微服务 |
| **第 2 章** | 拆分时机与原则 | Conway 定律、团队自治 |
| **第 3 章** | 拆分策略 | DDD 限界上下文、绞杀者模式 |
| **第 4 章** | 服务通信 | REST、gRPC、消息队列 |
| **第 5 章** | 数据拆分 | 数据库拆分、数据同步 |
---
## 1. 架构演进路径
架构演进不是技术驱动的,而是**组织规模驱动的**。当团队从 5 人增长到 500 人时,单体架构的协作效率会急剧下降。
| 阶段 | 架构 | 团队规模 | 特点 |
|------|------|---------|------|
| 起步期 | 单体应用 | 1~10 人 | 所有代码在一个项目中,部署简单 |
| 成长期 | 模块化单体 | 10~50 人 | 代码按模块划分,但仍然一起部署 |
| 扩张期 | SOA(面向服务) | 50~200 人 | 按业务线拆分为粗粒度服务 |
| 规模期 | 微服务 | 200+ 人 | 细粒度服务,每个团队独立开发部署 |
<ArchEvolutionDemo />
::: tip 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
不要一次性重写整个单体,而是像绞杀榕一样,逐步用新服务替换旧模块:
1. 在单体外部创建新服务
2. 通过代理层将部分流量路由到新服务
3. 验证新服务稳定后,逐步迁移更多流量
4. 最终完全替换旧模块
---
## 4. 服务通信模式
| 方式 | 协议 | 特点 | 适用场景 |
|------|------|------|---------|
| REST | HTTP/JSON | 简单通用,生态好 | 对外 API、CRUD 操作 |
| gRPC | HTTP/2 + Protobuf | 高性能,强类型 | 内部服务间高频调用 |
| 消息队列 | AMQP/Kafka | 异步解耦,削峰填谷 | 事件通知、异步任务 |
| GraphQL | HTTP/JSON | 客户端按需查询 | BFF 层、移动端 |
::: tip 同步 vs 异步的选择
- **需要立即返回结果** → 同步(REST/gRPC
- **不需要立即返回** → 异步(消息队列)
- **一个事件触发多个动作** → 异步(发布-订阅)
经验法则:能异步就异步,同步调用链越长,系统越脆弱。
:::
---
## 5. 数据拆分:最难的部分
微服务拆分中最痛苦的不是代码拆分,而是数据库拆分。每个服务应该拥有自己的数据库,但这意味着跨服务查询变得困难。
| 挑战 | 描述 | 解决方案 |
|------|------|---------|
| 跨服务 JOIN | 不能直接 JOIN 两个服务的表 | API 组合查询、数据冗余 |
| 分布式事务 | 跨库事务无法用本地事务 | Saga、本地消息表 |
| 数据一致性 | 多个服务的数据可能暂时不一致 | 最终一致性、事件驱动 |
| 数据迁移 | 从共享库迁移到独立库 | 双写过渡、数据同步工具 |
---
## 总结
从单体到微服务是一个渐进的过程,不是一蹴而就的革命。
回顾本章的关键要点:
1. **演进路径**:单体→模块化单体→SOA→微服务,每一步都有明确的驱动力
2. **拆分时机**:团队规模、部署冲突、扩容需求是拆分的信号
3. **拆分策略**:用 DDD 限界上下文指导拆分,用绞杀者模式渐进迁移
4. **通信选择**:能异步就异步,同步调用链越短越好
5. **数据拆分**:最难但最重要,接受最终一致性是关键心态转变
## 延伸阅读
- [Building Microservices](https://www.oreilly.com/library/view/building-microservices-2nd/9781492034018/) - Sam Newman 微服务经典
- [Monolith to Microservices](https://www.oreilly.com/library/view/monolith-to-microservices/9781492047834/) - 渐进式迁移指南
- [Domain-Driven Design](https://www.domainlanguage.com/ddd/) - Eric Evans 的 DDD 经典
- [The Strangler Fig Pattern](https://martinfowler.com/bliki/StranglerFigApplication.html) - Martin Fowler 的绞杀者模式