1. 引言:为什么需要微服务
单体应用在业务早期是最高效的选择:一个仓库、一次构建、一次部署,团队沟通成本低,调试链路清晰。但当业务规模、团队人数和迭代频率同时增长时,单体架构的代价开始显现:
- 构建与部署耦合:任何一行代码改动都要全量构建、全量发布,回归测试范围随代码量线性膨胀。
- 技术栈锁定:整个系统被迫使用同一套语言、框架和中间件版本。
- 故障扩散:一个非核心模块的内存泄漏可能拖垮整个进程。
- 团队协作瓶颈:多个团队在同一代码库上并行开发,合并冲突与发布排队成为常态。
微服务通过按业务边界拆分、独立部署、独立演进来缓解这些问题。但需要明确一点:微服务不是免费的午餐,它把「代码复杂度」换成了「分布式系统复杂度」。本文按「拆分 → 通信 → 治理 → 数据一致性」的顺序,梳理一套可落地的设计方法。
下图概括了本文的整体脉络,后续章节按此顺序展开:
2. 服务拆分的核心原则
2.1 单一职责:按业务能力而非技术分层拆分
最常见的错误是按技术分层拆分(Controller 服务、Service 服务、DAO 服务),这会导致每次业务变更都要跨多个服务发布,反而放大了耦合。
正确的做法是按业务能力(Business Capability)拆分,参考领域驱动设计(DDD)中的限界上下文(Bounded Context):
| 拆分方式 | 示例 | 评价 |
|---|---|---|
| 按技术分层 | 用户 Controller 服务 / 用户 DAO 服务 | ❌ 一个需求改多个服务 |
| 按业务能力 | 用户服务 / 订单服务 / 库存服务 | ✅ 变更内聚 |
| 按资源类型 | 图片服务 / 视频服务 | ⚠️ 需结合业务判断 |
判断一个服务是否「单一职责」,可以用一个简单标准:这个服务的所有变更理由是否属于同一个业务领域。如果两个功能总是因为不同原因被修改,就应该拆开;如果总是同时修改,就应该合并。
从单体到微服务的拆分演进路径可以这样理解:
2.2 独立部署:拆分的前提是能独立交付
一个服务只有具备以下能力,才算真正「独立」:
- 独立的数据存储:不与其他服务共享数据库表,避免隐式耦合。
- 独立的构建产物与发布流水线:可以单独发布而不影响其他服务。
- 独立的运行时资源:CPU、内存、连接池互不抢占。
- 向后兼容的接口契约:升级时不需要调用方同步发布。
如果拆分后两个服务仍然必须「一起发布」,那这次拆分只是增加了运维成本,没有带来收益。
2.3 去中心化:数据、治理与决策权下放
去中心化包含三个层面:
- 数据去中心化:每个服务拥有自己的数据,通过 API 或事件对外暴露,而不是让其他服务直连数据库。
- 治理去中心化:团队自主选择技术栈与实现方式,平台团队只提供标准化的基础设施(注册中心、配置中心、可观测性)。
- 决策去中心化:服务内部的实现细节由拥有该服务的团队决定,跨服务只约定契约。
注意:去中心化不等于「无标准」。接口协议、错误码规范、日志格式、链路追踪埋点这些跨服务契约必须统一,否则治理成本会急剧上升。
2.4 拆分粒度:从粗到细,避免过度拆分
拆分粒度没有标准答案,但有几条经验:
- 先粗后细:初期按 3~8 个核心域拆分,随着团队和业务增长再细化。
- 康威定律:系统架构会趋同于组织沟通结构,拆分应与团队边界对齐。
- 一个服务一个团队:如果一个服务需要三个团队共同维护,说明边界划错了。
- 警惕分布式单体:服务拆开了,但每次需求仍要同时改 5 个服务并一起发布,这就是「分布式单体」,比单体更难维护。
3. 服务通信模式
3.1 同步 vs 异步
| 维度 | 同步调用(HTTP/gRPC) | 异步消息(MQ/事件) |
|---|---|---|
| 耦合度 | 时间耦合,调用方需等待 | 时间解耦,生产消费互不阻塞 |
| 实时性 | 强,立即得到结果 | 弱,最终一致 |
| 故障影响 | 下游故障会向上传导 | 下游故障可缓冲、可重试 |
| 复杂度 | 链路清晰,易调试 | 需处理幂等、顺序、重复消费 |
| 适用场景 | 查询类、强一致校验 | 通知、状态流转、跨域事件 |
实践建议:
- 查询用同步,命令可异步:读操作通常需要即时结果,写操作若允许最终一致,优先异步。
- 核心链路同步,旁路异步:下单扣库存是核心链路,发短信、加积分、写审计日志应异步。
- 同步调用必须设置超时:没有超时的同步调用是故障放大器。
同步与异步的选择可以按下面的决策路径判断:
3.2 API 网关与 BFF
API 网关位于系统入口,承担横切关注点:
- 路由转发与协议转换
- 统一鉴权、限流、熔断
- 灰度发布与流量染色
- 访问日志与指标采集
BFF(Backend for Frontend) 是网关之后的一层聚合服务,为特定前端(Web、App、小程序)定制接口:
text
Web 前端 ──┐
App 前端 ──┼──> API 网关 ──> BFF(Web) ──┐
小程序 ────┘ BFF(App) ──┼──> 用户服务 / 订单服务 / 商品服务
└──> ...
BFF 的价值在于:
- 聚合裁剪:一次请求聚合多个下游服务,减少前端请求数。
- 端侧适配:不同端字段需求不同,避免后端接口为兼容多端而膨胀。
- 隔离变更:前端需求变化只影响 BFF,不波及核心服务。
代价是增加了一层调用与运维成本,小团队或端数量少时不必强行引入 BFF。
把上面的文本示意换成流程图,请求链路更清晰:
3.3 通信协议选型
- HTTP/REST:通用性好、调试方便,适合对外 API 与跨团队接口。
- gRPC:基于 HTTP/2 与 Protobuf,性能高、强类型契约,适合内部高频调用。
- 消息队列:Kafka、RocketMQ、RabbitMQ,适合事件驱动与削峰填谷。
4. 服务治理
4.1 注册发现
服务实例启动后向注册中心注册自身地址,调用方通过服务名而非 IP 寻址:
text
服务提供者 ──注册──> 注册中心(Nacos / Consul / Eureka)
│
调用方 ──订阅/拉取────────┘
│
└──> 负载均衡选择实例 ──> 发起调用
关键设计点:
- 健康检查:区分「注册存活」与「业务就绪」,避免流量打到未初始化完成的实例。
- 优雅上下线:下线前先从注册中心摘除,等待存量请求处理完再关闭进程。
- 本地缓存:注册中心不可用时,调用方应能使用本地缓存的服务列表继续工作。
一次完整的服务调用在注册发现体系中的流转如下:
4.2 配置管理
配置中心(Nacos Config、Apollo、Spring Cloud Config)解决三类问题:
- 环境隔离:dev / test / prod 配置分离,避免误用。
- 动态生效:限流阈值、开关、超时时间无需重启即可调整。
- 变更审计:谁在什么时候改了什么配置,可追溯、可回滚。
实践要点:
- 配置变更必须支持灰度推送,先推一个实例观察,再全量。
- 敏感配置(数据库密码、密钥)应加密存储,不要明文落在配置中心。
- 配置项要有默认值兜底,配置中心故障时服务仍能启动。
4.3 熔断降级
熔断器是一个状态机:
- Closed(关闭):正常放行请求,统计错误率。
- Open(打开):直接拒绝请求,快速失败,避免雪崩。
- Half-Open(半开):放行少量探测请求,成功则恢复,失败则继续熔断。
降级策略通常包括:
- 返回兜底数据:推荐服务不可用时返回热门榜单。
- 功能降级:关闭非核心功能(如商品评价),保证主流程可用。
- 限流保护:超过阈值直接拒绝,保护下游不被打垮。
配套的还有超时控制 与重试策略:重试必须配合退避与最大次数,且只对幂等操作重试,否则会放大故障。
4.4 可观测性
治理的前提是「看得见」。三个支柱缺一不可:
- 日志(Logging):结构化日志,统一 traceId 串联。
- 指标(Metrics):QPS、延迟分位值(P95/P99)、错误率、线程池与连接池水位。
- 链路追踪(Tracing):跨服务调用链,定位慢调用与异常节点。
5. 分布式事务与最终一致性
5.1 为什么需要分布式事务
服务拆分后,一个业务操作可能跨越多个服务的数据库。例如「下单」需要同时写订单库、扣库存、扣余额。本地事务无法覆盖跨库操作,于是产生一致性问题。
5.2 常见方案对比
| 方案 | 一致性 | 性能 | 侵入性 | 适用场景 |
|---|---|---|---|---|
| 2PC/XA | 强一致 | 低 | 高 | 内部少量强一致场景 |
| TCC | 最终一致 | 中 | 高 | 资金、库存等核心交易 |
| Saga | 最终一致 | 高 | 中 | 长流程、多步骤业务 |
| 本地消息表 | 最终一致 | 高 | 低 | 异步通知类场景 |
| 事务消息(RocketMQ) | 最终一致 | 高 | 低 | 与 MQ 深度集成的场景 |
5.3 实践:本地消息表 + 定时补偿
这是落地成本最低、最常用的最终一致性方案:
text
1. 本地事务:业务数据 + 消息记录 写入同一个本地事务
2. 异步投递:定时任务扫描未发送消息,投递到 MQ
3. 消费处理:下游消费并保证幂等
4. 状态回写:投递成功后更新消息状态为已发送
5. 失败补偿:超过最大重试次数进入死信,人工介入
关键点:
- 消息表与业务表在同一事务,保证「业务成功则消息一定落库」。
- 消费端必须幂等:用业务唯一键或去重表,避免重复消费导致重复扣款。
- 必须有补偿与告警:最终一致性依赖「一定会重试成功」,因此死信队列与监控告警不可省略。
5.4 一致性设计的三条底线
- 不要为了强一致牺牲可用性:绝大多数业务场景可以接受秒级最终一致。
- 幂等是一致性的前提:任何可能重试的写操作都必须幂等。
- 对账兜底:再完善的补偿也可能遗漏,核心资金类业务必须有定时对账机制。
6. 总结
微服务架构设计的核心不是「拆得多细」,而是边界清晰、契约稳定、治理到位:
- 拆分:按业务能力划分,保证独立部署,避免分布式单体。
- 通信:查询同步、命令异步,网关统一入口,BFF 按端适配。
- 治理:注册发现保证可达,配置管理保证可调,熔断降级保证可用,可观测性保证可查。
- 一致性:优先最终一致,幂等与补偿是必备能力,核心业务对账兜底。
架构演进没有终点,建议从最小可行的拆分开始,用监控数据驱动下一步优化,而不是一次性设计出「完美架构」。