一次讲清微服务架构:服务拆分、通信、数据一致性与工程落地

提到微服务,很多人首先想到的是:

  • 把一个项目拆成很多服务;
  • 每个服务提供一组 API;
  • 用 Docker 打包;
  • 用 Kubernetes 部署;
  • 服务之间再加一个消息队列。

这些都可能出现在微服务系统里,却不是微服务的本质。

一个系统即使有几十个服务,如果每次上线仍要一起发布、多个服务直接修改同一批数据、一次需求改动要协调所有团队,它依然可能只是一个“分布式单体”。

真正需要回答的问题是:

系统是否围绕业务能力形成了清晰边界,并让各部分能够独立开发、部署、扩展和承担运行责任?

本文将从单体与微服务的选择开始,依次讲清服务拆分、服务通信、数据一致性、容错、可观测性、测试发布,以及如何从一个模块化单体渐进演化到微服务。

一、架构到底是什么

架构不是一个框架,也不等于接口设计。它描述的是系统的整体组织方式,包括:

  • 系统由哪些部分组成;
  • 每个部分负责什么;
  • 各部分如何通信;
  • 数据由谁拥有;
  • 请求从哪里进入;
  • 应用如何部署、扩容和回滚;
  • 某个部分失败时系统如何响应;
  • 出现问题后如何定位;
  • 身份、权限、配置和密钥如何管理。

因此,微服务不是一套固定技术栈,而是一种组织复杂软件及其团队的架构风格。

二、什么是微服务

微服务架构把应用按照业务能力划分为一组自治服务。每个服务具有相对清晰的职责和数据边界,通过 API 或消息与其他服务协作,并能够独立开发、测试、部署和扩展。

例如,一个电商系统可能包含:

  • 用户服务;
  • 商品服务;
  • 购物车服务;
  • 订单服务;
  • 支付服务;
  • 库存服务;
  • 物流服务;
  • 通知服务。

重点并不是“服务小”或“服务多”,而是下面几项约束:

  1. 服务围绕业务能力建立边界;
  2. 服务内部高内聚,服务之间低耦合;
  3. 一个服务可以独立发布,而不要求其他服务同时上线;
  4. 数据有明确的所有者;
  5. 服务故障不会不受控制地向全系统扩散;
  6. 团队能够独立负责服务的开发和运行。

微软的微服务架构指南同样强调业务能力、限界上下文、独立部署和服务私有数据。也就是说,微服务首先是边界和自治问题,其次才是框架与基础设施问题。

三、单体、模块化单体与微服务怎么选

很多系统真正需要比较的不是“单体还是微服务”,而是下面三种形态。

维度普通单体模块化单体微服务
部署单元一个一个多个
模块调用进程内调用受约束的进程内调用网络调用或消息
数据管理经常共享可划分逻辑边界原则上由服务各自拥有
本地开发简单较简单较复杂
事务本地事务为主本地事务为主经常需要最终一致性
独立扩容较弱较弱较强
独立发布不支持通常不支持核心目标之一
运维成本低中低高
适合阶段小型或早期系统业务增长但边界仍在形成复杂领域、多团队、独立演进需求明确

1. 普通单体

单体应用把主要功能放在一个部署单元中。它并不天然落后,反而具有明显优势:

  • 启动和调试简单;
  • 调用不经过网络;
  • 本地事务容易实现;
  • 部署链路短;
  • 小团队协作成本低。

问题通常不在“单体”二字,而在于内部缺少边界:模块随意互相调用、数据表被到处修改、业务规则散落在各层。

2. 模块化单体

模块化单体仍然整体部署,但内部按业务模块组织,并约束模块之间的依赖和数据访问。

它保留了单体的开发、测试和事务优势,又为未来拆分服务建立了边界,是很多新项目更稳妥的起点。

3. 微服务

微服务把部分模块变成独立的部署和运行单元。它用更高的分布式系统成本,换取:

  • 独立发布;
  • 独立扩容;
  • 故障隔离;
  • 团队自治;
  • 不同业务能力独立演进。

因此,选择微服务之前,必须先确认这些收益是否真的存在。

四、微服务解决什么问题

微服务主要适合以下矛盾逐渐突出的系统。

1. 发布节奏互相阻塞

一个小功能也必须重新构建和发布整个应用,多支团队的变更长期挤在同一条发布流水线上。

服务独立部署后,团队可以按各自节奏交付。但前提是接口保持兼容,也不能存在“名义独立、实际必须联动发布”的隐藏依赖。

2. 不同模块的扩展需求差异很大

例如商品查询流量很高,后台对账任务计算量很大,而用户资料修改请求很少。单体只能整体扩容,微服务则可以只扩容压力最大的部分。

3. 团队规模和业务复杂度持续增长

当多个团队频繁修改同一代码库、共享同一批数据库表时,技术依赖会转化为沟通和协调成本。

清晰的服务边界可以让一个跨职能团队端到端负责某项业务能力,但团队边界也应与业务边界对齐。

4. 不同业务能力需要不同可靠性策略

核心下单流程和营销推荐的可用性要求可能不同。拆分后,可以分别设计资源隔离、容量、降级和恢复策略。

需要注意:服务拆开并不会自动获得故障隔离。如果调用链过长、超时设置不合理、重试失控,故障反而会通过网络调用快速扩散。

五、微服务带来了哪些新成本

微服务不是把原有复杂度消灭,而是改变了复杂度的位置。

1. 本地调用变成网络调用

一次函数调用通常成功或直接抛出错误,而远程调用还可能出现:

  • 请求根本没有到达;
  • 请求到达但响应丢失;
  • 响应速度异常;
  • 客户端超时后,服务端仍然完成了操作;
  • 重试导致同一操作执行多次;
  • 消息重复、延迟或乱序。

因此,超时、重试、幂等、熔断和降级不再是可选细节。

2. 本地事务变成分布式一致性

在单体中,“创建订单、扣减库存、记录支付”可能放在一个数据库事务里。拆成多个服务后,每个服务只能可靠地提交自己的本地事务。

系统必须定义:

  • 哪些数据必须立即一致;
  • 哪些状态允许短暂不一致;
  • 后续步骤失败时如何补偿;
  • 重复消息如何处理;
  • 如何确认整个业务流程最终完成。

3. 调试变得困难

一个请求可能经过网关、订单、库存、支付和消息系统。只查看某个进程的日志,很难还原完整过程。

系统需要统一的日志字段、指标、链路追踪和告警。

4. 交付和运维成本上升

更多服务意味着更多:

  • 构建与发布流水线;
  • 配置和密钥;
  • 健康检查;
  • 版本兼容问题;
  • 容量与成本管理;
  • 值班、告警和故障恢复责任。

一句话总结:

微服务不是为了让简单系统看起来更高级,而是为了让已经复杂的系统仍能持续演进。

六、服务应该怎么拆

服务拆分是微服务设计中最关键、也最容易出错的部分。

1. 按业务能力拆,不按技术层拆

较好的划分方式:

  • 订单;
  • 支付;
  • 库存;
  • 物流;
  • 通知。

容易产生问题的划分方式:

  • Controller 服务;
  • Service 服务;
  • Repository 服务;
  • 数据库访问服务。

按技术层拆分会让一次业务请求跨越大量服务。所有服务必须一起修改和发布,网络调用增多,业务边界却没有变清晰。

2. 不要按数据库表或接口数量机械拆分

“一张表一个服务”和“一个接口一个服务”通常会制造过细的服务。服务的粒度应由业务一致性、变更频率、团队责任和运行特征共同决定。

领域驱动设计中的 Bounded Context(限界上下文)可以帮助寻找边界:在一个边界内,业务术语、规则和数据模型保持一致;跨越边界时,通过明确契约协作。

3. 用四个问题检查候选边界

对一个候选服务,可以询问:

  1. 它是否代表一项相对完整的业务能力?
  2. 它能否在不修改其他服务的情况下独立演进?
  3. 它拥有的数据和业务规则是否清晰?
  4. 一个团队能否端到端负责其开发、部署和运行?

如果答案长期是否定的,边界很可能不合理。

4. 识别“分布式单体”

常见信号包括:

  • 服务很多,但每次必须一起发布;
  • 多个服务直接读写同一组表;
  • 一个需求要同时修改许多服务;
  • 同步调用链很长;
  • 某个下游变慢会拖慢整条链路;
  • 本地启动必须拉起十几个服务;
  • 服务名不同,业务责任却互相重叠。

服务拆得越细,不代表架构越好。边界不确定时,先保持较粗粒度通常更容易调整。

七、同步通信与异步通信

微服务常见的通信方式可以分为两类。

方式常见实现适合场景主要代价
同步通信HTTP、REST、gRPC调用方必须立即得到结果时间耦合、级联失败、长调用链
异步通信消息队列、事件流后台处理、削峰、跨服务通知最终一致性、重复与乱序、排障更难

1. 什么时候用同步调用

当调用方必须根据结果立即继续时,同步调用更自然,例如:

  • 查询商品详情;
  • 校验当前库存;
  • 获取用户权限;
  • 请求支付渠道创建支付单。

每个远程调用都应该考虑:

  • 明确的超时时间;
  • 只对适合的错误重试;
  • 退避策略和最大重试次数;
  • 幂等设计;
  • 熔断、舱壁隔离与降级;
  • 请求标识和链路上下文传播。

重试不是越多越可靠。没有退避和上限的重试,可能在下游故障时制造更大的流量压力。

2. 什么时候用异步消息

当调用方不需要立即得到最终结果,或者需要削峰、解耦时,可以使用异步消息,例如:

  • 订单创建后发送通知;
  • 支付成功后更新积分;
  • 用户行为进入分析流水线;
  • 耗时任务进入后台处理。

需要区分两个概念:

  • Command(命令):希望某个接收方执行一项动作;
  • Event(事件):声明某件事已经发生,发布者不指定所有后续行为。

使用消息系统后,还必须处理重复投递、消费失败、乱序、死信和消息积压。

八、数据归属与分布式一致性

服务自治的重要基础是数据归属。

1. 服务拥有自己的数据

理想情况下,只有订单服务能够修改订单数据,只有库存服务能够修改库存数据。其他服务通过 API、事件或专门的读取模型获取所需信息。

“数据归属独立”并不等于项目第一天就必须购买多个数据库实例。早期可以共享数据库基础设施,但应保持逻辑边界:

  • 表由明确模块或服务拥有;
  • 禁止跨边界直接写表;
  • 跨边界访问经过稳定契约;
  • 为未来物理拆分保留可能。

2. 跨服务查询怎么办

常见方案包括:

  • API Composition:由聚合层调用多个服务并组合结果;
  • 数据副本或快照:订阅事件,保留查询所需的局部数据;
  • CQRS 读取模型:为复杂查询建立专门的读模型。

选择取决于实时性、查询复杂度、数据量和可接受的一致性窗口。

3. Saga 解决跨服务业务流程

Saga 把一个跨服务事务拆成多个本地事务。每一步成功后触发下一步;某一步失败时,执行事先设计的补偿操作。

以创建订单为例:

创建待支付订单
    ↓
预留库存
    ↓
创建支付
    ↓
支付成功 → 确认订单

任一步失败
    ↓
执行对应补偿:释放库存、关闭支付单、取消订单

补偿不一定能把现实世界完全“回滚”。例如支付退款可能产生手续费,物流发出后也不能像数据库事务一样撤销。因此,补偿规则本身就是业务设计。

4. Outbox 避免数据库与消息“双写”

一个常见风险是:

  1. 业务数据已经提交;
  2. 消息发布失败;
  3. 其他服务永远不知道这次变更。

Transactional Outbox(事务发件箱)模式把业务变更和待发送事件写入同一个本地事务,再由独立发布器把 Outbox 记录发送到消息系统。

它能减少“双写不一致”,但消费者仍要具备幂等性,因为消息可能被重复发送。

九、网关、服务发现和流量入口

1. API Gateway

网关通常作为外部客户端的统一入口,负责:

  • 路由;
  • 身份认证;
  • 限流;
  • 日志与请求标识;
  • 协议转换;
  • 灰度路由;
  • 必要的响应聚合。

业务规则不应大量进入网关,否则网关会变成新的集中式瓶颈和耦合中心。

2. 服务发现

服务实例可能随扩缩容和故障恢复而变化,调用方不应硬编码实例地址。服务发现把稳定的服务名称映射到当前可用实例。

例如在 Kubernetes 中,Service 为一组动态 Pod 提供稳定的网络入口,并可通过集群 DNS 被其他工作负载发现。

十、容错与稳定性设计

分布式系统中,部分失败是常态。常用机制包括:

  • Timeout:避免请求无限等待;
  • Retry:对瞬时、可恢复错误有限重试;
  • Idempotency:重复请求不会产生额外副作用;
  • Circuit Breaker:下游持续失败时暂停调用;
  • Fallback:返回缓存、默认值或受限能力;
  • Rate Limiting:控制进入系统的请求速率;
  • Bulkhead:隔离线程池、连接池或资源配额;
  • Health Check:区分进程存活与能否接收流量;
  • Graceful Shutdown:停止接流量后完成或移交在途任务。

这些机制必须组合设计。例如,超时过长会占用资源,重试次数过多会放大流量,错误的降级结果则可能破坏业务正确性。

十一、可观测性不是上线后的补丁

一个微服务系统至少需要三类信号。

1. 日志

日志应尽量结构化,并包含:

  • 时间;
  • 服务名和环境;
  • 请求或链路标识;
  • 关键业务实体标识;
  • 错误类型和上下文;
  • 必要的版本信息。

注意不要在日志中记录密码、Token、密钥和不必要的个人信息。

2. 指标

至少关注:

  • 请求量;
  • 错误率;
  • 延迟及 P95、P99;
  • CPU、内存、连接池等资源;
  • 队列积压;
  • 订单成功率等业务指标。

3. 链路追踪

Trace 描述一个请求如何跨越多个服务,Span 表示其中一段操作。统一传播 trace_id 或等价上下文后,才能定位慢请求究竟卡在网关、数据库还是下游服务。

OpenTelemetry 将 Traces、Metrics 和 Logs 作为主要可观测信号,并提供跨语言的采集规范。工具本身不是目标,目标是让团队能够回答:

  • 系统哪里出错了?
  • 影响了哪些用户和业务?
  • 从什么时候开始?
  • 根因在哪一个服务或依赖?

十二、安全、测试和交付是微服务的前置能力

1. 安全

微服务安全不能只依赖网关:

  • 网关可以执行统一身份认证和基础策略;
  • 服务仍需校验业务权限;
  • 服务间通信需要可信身份和传输保护;
  • 密钥应由专门机制管理;
  • 多租户数据必须实施隔离;
  • 敏感操作需要审计。

如果任何内部服务都被默认信任,一旦攻击者进入内网,风险会迅速扩大。

2. 测试

常见测试层次包括:

  • 单元测试:验证单个规则和函数;
  • 集成测试:验证数据库、缓存、消息等真实依赖;
  • 契约测试:验证服务提供方与消费方的接口约定;
  • 端到端测试:验证少量关键业务链路。

微服务不应依靠大量脆弱的端到端测试兜底。单元、集成和契约测试应尽早发现大部分问题。

3. CI/CD 与发布

独立部署只有在自动化交付成立时才有价值。一条服务流水线通常需要完成:

  • 静态检查;
  • 单元和集成测试;
  • 契约测试;
  • 构建制品;
  • 安全扫描;
  • 测试环境部署;
  • 冒烟验证;
  • 生产发布;
  • 监控与回滚。

发布还要处理 API、消息格式、数据库迁移和配置的向后兼容。代码可以回滚,不代表数据库和已经发出的消息也能自动回滚。

十三、Docker 和 Kubernetes 分别解决什么

Docker 主要解决应用及其依赖如何被一致打包和运行的问题。

Kubernetes 主要解决容器化工作负载如何被调度、服务发现、扩缩容、自愈和滚动更新的问题。

它们都可以支撑微服务,但不会替你解决:

  • 服务边界;
  • 业务一致性;
  • 接口耦合;
  • 团队责任;
  • 领域模型。

一个边界混乱的系统部署到 Kubernetes 后,只会变成一个运行在 Kubernetes 上的边界混乱系统。

十四、用电商系统理解渐进式拆分

假设我们从一个电商项目开始。

阶段一:先做模块化单体

E-commerce Application
├── User Module
├── Catalog Module
├── Cart Module
├── Order Module
├── Payment Module
├── Inventory Module
└── Notification Module

所有模块仍在一个进程中,但需要明确:

  • 模块公开哪些接口;
  • 哪些数据由模块拥有;
  • 其他模块不能绕过接口直接修改其数据;
  • 模块之间的依赖方向。

这一步能够用较低成本验证业务边界。

阶段二:先抽取低风险、弱事务模块

通知通常是一个较适合的候选:

Monolith ── OrderCreated Event ──> Notification Service

通知失败不应阻止订单创建,异步消息可以降低时间耦合。此时可以练习:

  • 消息投递;
  • 重复消费幂等;
  • 失败重试和死信;
  • 服务日志与追踪;
  • 独立构建和部署。

阶段三:按真实压力继续抽取

当支付、库存已经具备稳定边界,并且存在独立发布、容量或团队责任需求时,再考虑抽取。

典型流程可能是:

Client
  ↓
API Gateway
  ↓
Order Service
  ├── Inventory Service
  ├── Payment Service
  └── Event Bus ──> Notification Service

订单服务需要通过状态机描述“待支付、已支付、已取消、履约中”等状态,而不是假设所有步骤会在一次同步请求中同时成功。

阶段四:按运维压力引入编排平台

当服务数量、实例数量、部署频率和环境复杂度确实需要统一调度时,再引入 Kubernetes 或托管容器平台。

不要为了学习 Kubernetes 而先制造几十个微服务。架构演进应由问题驱动。

十五、什么时候不应该使用微服务

以下情况下,模块化单体往往更合适:

  • 团队规模较小,沟通成本尚未成为瓶颈;
  • 业务模型频繁变化,边界还不稳定;
  • 系统规模不大,整体发布和扩容没有明显问题;
  • 缺少自动化测试和部署流水线;
  • 没有统一日志、指标、链路和告警;
  • 团队尚未准备承担分布式数据和网络故障;
  • 服务没有明确负责人和运行责任。

微服务的收益必须大于它引入的协调、基础设施和故障处理成本。

十六、什么时候可以认真考虑微服务

可以寻找这些证据:

  • 某些业务模块需要频繁独立发布;
  • 不同模块流量差异明显,需要独立扩缩容;
  • 多支团队频繁在同一代码和数据边界上冲突;
  • 业务边界已经相对稳定;
  • 某些能力需要明确的故障和资源隔离;
  • CI/CD、可观测性、值班和故障恢复机制已经较成熟;
  • 团队能够端到端负责服务生命周期。

这里的关键词是“证据”,而不是“未来也许会变大”。

十七、一条更稳妥的学习与落地路线

推断:结合本文整理的工程约束,一条低风险路线如下。它不是所有团队唯一的答案,但比一开始直接堆叠基础设施更容易验证架构价值。

第一步:建立模块化单体

  • 用一个熟悉的后端框架完成核心业务;
  • 按业务模块组织代码;
  • 明确模块 API 和数据归属;
  • 使用本地事务保证核心正确性。

第二步:补齐工程基础

  • 用 Docker Compose 运行应用、数据库和缓存;
  • 建立自动化测试与 CI;
  • 加入结构化日志、请求标识、指标和健康检查;
  • 明确配置与密钥管理。

第三步:抽取一个低风险服务

  • 选择通知、文件处理等边界清晰的能力;
  • 通过 Strangler Fig(绞杀者模式)逐步转发流量;
  • 建立独立构建、发布和监控;
  • 验证服务归属是否真的减少了协调成本。

第四步:练习可靠通信

  • 同步调用设置超时、有限重试和熔断;
  • 异步消费处理重复、失败和积压;
  • 对有副作用的操作实现幂等;
  • 传播链路上下文。

第五步:再处理跨服务业务

  • 定义一致性要求;
  • 按需要引入 Saga 和 Outbox;
  • 设计业务状态机与补偿动作;
  • 通过契约测试保护接口和消息兼容性。

第六步:最后学习集群编排

  • 理解 Pod、Deployment、Service 和配置管理;
  • 实践滚动发布、扩缩容、就绪检查和优雅退出;
  • 用真实运行压力验证是否需要更复杂的平台能力。

十八、微服务决策清单

在决定拆分某个服务前,可以逐项回答:

业务边界

  • 这个服务代表什么业务能力?
  • 哪些规则和数据属于它?
  • 它与其他服务的契约是什么?

独立性

  • 它能否独立开发、测试、部署和回滚?
  • 一次变更是否仍要求多个服务同时发布?
  • 团队是否能够独立负责其运行?

数据

  • 谁可以修改核心数据?
  • 跨服务查询如何实现?
  • 允许多长时间的不一致?
  • 失败时如何补偿和对账?

可靠性

  • 超时、重试和幂等如何设计?
  • 下游不可用时如何降级?
  • 如何避免故障级联?

运维

  • 如何查看日志、指标和完整调用链?
  • 如何告警、发布和回滚?
  • 谁负责处理生产故障?

如果大部分问题没有答案,优先完善模块化单体通常比继续拆分更有价值。

十九、常见误区

误区一:服务越小越好

过细的服务会增加网络调用、数据一致性和运维成本。好的边界比小的代码量更重要。

误区二:每个服务必须使用不同技术栈

技术自治是一种可能性,不是目标。过多语言和框架会增加招聘、升级、安全和运维成本。团队应在自治与平台标准化之间取平衡。

误区三:用了消息队列就解耦了

如果事件格式频繁变化、消费者依赖发布者内部字段,或者所有流程都依赖固定事件顺序,耦合只是从调用接口转移到了消息契约。

误区四:网关负责所有权限

网关适合统一认证和通用策略,服务仍需验证与业务资源相关的授权规则。

误区五:先上 Kubernetes,再设计服务

Kubernetes 管理运行单元,不负责发现业务边界。先解决领域和团队问题,再决定部署平台。

二十、面试时如何简洁回答

可以这样概括:

微服务是一种按业务能力划分系统的架构风格。每个服务拥有清晰职责和数据边界,能够独立开发、部署和扩展,并通过 API 或消息协作。它适合复杂领域、多团队和独立演进需求明显的系统,但会引入网络故障、分布式一致性、可观测性和交付运维成本。因此,实践中通常先建立模块化单体,再根据真实的发布、扩展和团队协作压力渐进拆分。

如果继续追问,应重点展开:

  1. 如何识别服务边界;
  2. 同步与异步通信如何选择;
  3. Saga、Outbox 和幂等如何维护一致性;
  4. 超时、重试、熔断和隔离如何避免级联故障;
  5. 什么情况下不应该使用微服务。

总结

微服务最重要的不是“把代码拆开”,而是建立可独立演进的业务边界。

判断一个微服务设计是否有效,可以看五件事:

  1. 边界是否围绕业务能力;
  2. 服务是否真正能够独立发布;
  3. 数据是否有清晰所有者;
  4. 网络失败和一致性问题是否被显式设计;
  5. 团队是否具备测试、交付、观测和运行能力。

对于多数新项目,更可靠的起点是模块化单体。先把边界做好,再让真正需要独立演进的模块跨越进程边界。

架构没有免费的收益。微服务的价值,不在于服务数量,而在于它引入的复杂度是否换来了更低的长期变更成本。

如果这篇文章帮你建立了微服务架构的整体认知,欢迎点赞、收藏,方便后续学习服务拆分、服务通信、数据一致性、容错治理和部署运维时随时回来复习。

你在学习或实践微服务时遇到过哪些问题?欢迎在评论区一起交流。后续我还会继续分享微服务设计、系统架构和后端工程实战,感兴趣的话点个关注,我们下一篇见!

参考资料

更多推荐