微服务架构模式:从单体到分布式
// 目录 · contents
前言
随着业务规模的增长,单体架构逐渐暴露出部署困难、扩展受限、技术栈锁定等问题。微服务架构通过将系统拆分为一组小型、自治的服务来解决这些问题。但微服务并非银弹,它引入了分布式系统的复杂性。本文将系统性地梳理微服务架构中的核心模式,帮助你在实际项目中做出正确的架构决策。
单体架构 vs 微服务架构
单体架构的特征
单体架构将所有功能模块打包在一个部署单元中,共享同一个数据库。它的优势在于开发简单、部署方便、调试直观。
1 | |
单体架构的痛点
当团队规模超过 10 人、代码量超过 50 万行时,单体架构的问题开始显现:
- 部署耦合:修改一行代码需要重新部署整个应用
- 扩展受限:无法针对热点模块单独扩容
- 技术锁定:所有模块必须使用相同的技术栈
- 故障传播:一个模块的 OOM 会导致整个应用崩溃
微服务架构全景
graph TB
Client[客户端] --> Gateway[API Gateway]
Gateway --> UserService[用户服务]
Gateway --> OrderService[订单服务]
Gateway --> PaymentService[支付服务]
Gateway --> InventoryService[库存服务]
UserService --> UserDB[(用户DB)]
OrderService --> OrderDB[(订单DB)]
PaymentService --> PaymentDB[(支付DB)]
InventoryService --> InventoryDB[(库存DB)]
OrderService -->|异步消息| MQ[消息队列]
MQ --> PaymentService
MQ --> InventoryService
subgraph Service Mesh
UserService
OrderService
PaymentService
InventoryService
end
服务分解策略
按业务能力分解(Business Capability)
根据企业的业务能力来划分服务边界。业务能力是企业为产生价值而做的事情。
graph LR
subgraph 电商平台
subgraph 商品管理
A[商品目录服务]
B[库存服务]
end
subgraph 订单管理
C[订单服务]
D[物流服务]
end
subgraph 用户管理
E[账户服务]
F[认证服务]
end
subgraph 支付管理
G[支付服务]
H[结算服务]
end
end
按子域分解(DDD Subdomain)
基于领域驱动设计(DDD)的限界上下文来定义服务边界,这是目前最被推荐的分解方式。
1 | |
分解原则
- 单一职责:每个服务只负责一个业务能力
- 高内聚低耦合:服务内部紧密相关,服务之间松散依赖
- 数据自治:每个服务拥有并管理自己的数据库
- 合理粒度:不要过度拆分,一个小团队(2-pizza team)能维护一个服务
API Gateway 模式
API Gateway 是微服务架构中的核心基础设施,它作为系统的统一入口,承担请求路由、协议转换、认证授权、限流熔断等职责。
sequenceDiagram
participant C as 客户端
participant G as API Gateway
participant A as 认证服务
participant O as 订单服务
participant U as 用户服务
C->>G: GET /api/orders/123
G->>A: 验证 Token
A-->>G: Token 有效, userId=456
par 并行调用
G->>O: GET /orders/123
G->>U: GET /users/456
end
O-->>G: 订单数据
U-->>G: 用户数据
G->>G: 聚合响应数据
G-->>C: 完整订单信息(含用户)
BFF 模式(Backend for Frontend)
不同的客户端(Web、移动端、第三方)有不同的数据需求,可以为每种客户端提供专属的 API Gateway。
graph TB
Web[Web App] --> WebBFF[Web BFF]
Mobile[Mobile App] --> MobileBFF[Mobile BFF]
ThirdParty[第三方] --> OpenAPI[Open API Gateway]
WebBFF --> Services[微服务集群]
MobileBFF --> Services
OpenAPI --> Services
1 | |
Service Mesh 模式
Service Mesh 将服务间通信的复杂性从应用代码中剥离出来,下沉到基础设施层。每个服务实例旁边部署一个 Sidecar 代理,由它负责所有的网络通信。
graph TB
subgraph Pod A
ServiceA[服务 A] <--> ProxyA[Envoy Proxy]
end
subgraph Pod B
ServiceB[服务 B] <--> ProxyB[Envoy Proxy]
end
subgraph Pod C
ServiceC[服务 C] <--> ProxyC[Envoy Proxy]
end
ProxyA <-->|mTLS| ProxyB
ProxyB <-->|mTLS| ProxyC
ProxyA <-->|mTLS| ProxyC
ControlPlane[控制平面 Istiod] -->|配置下发| ProxyA
ControlPlane -->|配置下发| ProxyB
ControlPlane -->|配置下发| ProxyC
Service Mesh 提供的核心能力: - 流量管理:负载均衡、熔断、超时、重试、灰度发布 - 安全通信:mTLS 加密、访问控制 - 可观测性:分布式追踪、指标采集、访问日志
Saga 模式
在微服务架构中,跨服务的事务不能使用传统的 ACID 事务。Saga 模式通过一系列本地事务和补偿操作来维护数据一致性。
编排式 Saga(Orchestration)
由一个中央协调器(Orchestrator)来控制整个 Saga 的执行流程。
sequenceDiagram
participant Orch as Saga Orchestrator
participant Order as 订单服务
participant Payment as 支付服务
participant Inventory as 库存服务
participant Delivery as 物流服务
Orch->>Order: 创建订单
Order-->>Orch: 订单已创建
Orch->>Payment: 扣款
Payment-->>Orch: 扣款成功
Orch->>Inventory: 扣减库存
Inventory-->>Orch: 库存不足!
Note over Orch: 触发补偿流程
Orch->>Payment: 退款(补偿)
Payment-->>Orch: 退款成功
Orch->>Order: 取消订单(补偿)
Order-->>Orch: 订单已取消
1 | |
协同式 Saga(Choreography)
没有中央协调器,每个服务监听事件并决定下一步操作。
graph LR
A[订单服务] -->|OrderCreated| B[支付服务]
B -->|PaymentCompleted| C[库存服务]
C -->|InventoryReserved| D[物流服务]
C -->|InventoryFailed| B
B -->|PaymentRefunded| A
A -->|OrderCancelled| E[结束]
编排式适合流程复杂、参与者较多的场景,便于理解和维护。协同式适合简单流程,避免了单点故障,但流程复杂时难以追踪。
事件驱动架构
事件驱动架构(EDA)是微服务间通信的核心模式之一,它实现了服务间的松耦合。
graph LR
subgraph 生产者
OrderService[订单服务]
UserService[用户服务]
end
subgraph 事件总线
Topic1[order-events]
Topic2[user-events]
end
subgraph 消费者
Analytics[分析服务]
Notification[通知服务]
Search[搜索服务]
end
OrderService -->|publish| Topic1
UserService -->|publish| Topic2
Topic1 -->|subscribe| Analytics
Topic1 -->|subscribe| Notification
Topic1 -->|subscribe| Search
Topic2 -->|subscribe| Analytics
Topic2 -->|subscribe| Notification
1 | |
绞杀者模式(Strangler Fig Pattern)
绞杀者模式是将单体架构逐步迁移到微服务架构的最佳策略。就像绞杀榕缠绕宿主树一样,新的微服务逐渐替代旧系统的功能。
graph TB
subgraph "阶段1: 初始状态"
Client1[客户端] --> Monolith1[单体应用<br/>用户/订单/支付/库存]
end
subgraph "阶段2: 开始迁移"
Client2[客户端] --> Facade2[反腐层/Facade]
Facade2 --> Monolith2[单体应用<br/>用户/支付/库存]
Facade2 --> OrderMS2[订单微服务]
end
subgraph "阶段3: 逐步替换"
Client3[客户端] --> Facade3[API Gateway]
Facade3 --> Monolith3[单体应用<br/>库存]
Facade3 --> OrderMS3[订单微服务]
Facade3 --> UserMS3[用户微服务]
Facade3 --> PayMS3[支付微服务]
end
subgraph "阶段4: 完成迁移"
Client4[客户端] --> Gateway4[API Gateway]
Gateway4 --> OrderMS4[订单微服务]
Gateway4 --> UserMS4[用户微服务]
Gateway4 --> PayMS4[支付微服务]
Gateway4 --> InvMS4[库存微服务]
end
实施步骤
- 识别边界:在单体中找到可以独立拆分的模块
- 建立反腐层:在新旧系统之间建立适配层
- 逐步迁移:将流量从旧模块路由到新服务
- 数据迁移:将数据从共享数据库迁移到服务专属数据库
- 退役旧代码:确认新服务稳定后,删除单体中的对应模块
1 | |
微服务架构决策框架
在采用微服务之前,需要回答以下问题:
| 维度 | 适合微服务 | 适合单体 |
|---|---|---|
| 团队规模 | > 10 人 | < 10 人 |
| 系统复杂度 | 多个业务域 | 单一业务域 |
| 发布频率 | 各模块独立发布 | 统一发布即可 |
| 扩展需求 | 各模块扩展需求不同 | 整体扩展即可 |
| 技术多样性 | 需要不同技术栈 | 统一技术栈 |
| 运维能力 | 有 DevOps 文化和工具链 | 运维能力有限 |
总结
微服务架构不是目标,而是手段。在实施微服务架构时,需要根据团队能力、业务复杂度和技术基础设施来选择合适的模式:
- API Gateway 解决统一入口和请求聚合的问题
- Service Mesh 将通信复杂性下沉到基础设施层
- Saga 模式 解决分布式事务的一致性问题
- 事件驱动架构 实现服务间的松耦合
- 绞杀者模式 提供从单体到微服务的平滑迁移路径
记住 Martin Fowler 的建议:Monolith First。先用单体架构快速验证业务,在确认需要时再逐步演进到微服务。
踩坑记录
我们第一次服务拆分,按数据库表来拆,把用户模块拆成了「用户基础信息服务」、「用户地址服务」、「用户积分服务」三个。结果一个「查询用户完整信息」的接口要调用 3 个服务,N+1 查询问题比单体时代更严重,链路延迟从 20ms 变成 200ms,还多了 3 倍的网络故障点。
根本原因是按表拆而不是按业务能力拆。表是实现细节,不是业务边界。
后来重新梳理,按照 DDD
限界上下文重新划分:User Profile(身份/地址)、User Behavior(积分/历史)、User Account(余额/支付)。每个服务内部数据完整,跨服务只有事件通知。重新拆完后,核心接口从
3 次 RPC 变回 1 次,延迟降到 12ms,故障影响范围也大幅缩小。
实测结果
| 阶段 | 部署窗口 | 核心接口 P99 | 服务间依赖数 | 故障影响范围 |
|---|---|---|---|---|
| 单体 | 每两周一次,全量发布 | 20ms | 0 | 全部功能 |
| 按表拆分后 | 可独立,但耦合严重 | 200ms | 3 次 RPC | 仍波及全链路 |
| 按业务能力拆分后 | 完全独立 | 12ms | 1 次 RPC | 故障隔离有效 |
我的看法
「按表拆」是微服务拆分最常见的错误。DDD 的限界上下文真正有价值的地方在于:它告诉你什么数据应该在同一个服务里,以及服务间的数据冗余是被允许的。
不要为了「单一职责」而拆到每个服务只有一张表。微服务的边界应该和团队边界对齐(Conway’s Law),一个团队能独立负责、独立发布的才是合理的服务边界。如果两个服务经常需要同时修改、同时发布,那它们根本就不应该被拆开。