2. 微服务数据架构原则

在单体应用中,所有业务模块共享同一个数据库,通过本地事务的 ACID 特性就能保证数据一致。但当我们把系统拆分为订单、库存、支付、用户等多个微服务后,数据架构面临一个根本性转变:每个服务拥有自己独立的数据库,服务之间不再共享表,也不能依赖跨库的本地事务。这一节我们围绕四条核心原则展开:每个服务独立数据库、数据去中心化、最终一致性、以及支撑后两者的 Saga 模式与事件驱动架构。

一、每个服务独立数据库

“独立数据库”包含两层含义:一是私有,即数据库只能由所属服务访问,其他服务不得直连其表;二是自治,即该服务的表结构变更、索引调整、存储选型都由本团队决定,不需要与其他服务协调。例如订单服务使用 MySQL 存储订单主表,库存服务使用 PostgreSQL 存储库存流水,商品搜索服务则可能使用 Elasticsearch。它们之间没有外键约束,也没有共享的 JOIN 查询。

这样做的好处是解耦:订单服务的表结构变化不会直接破坏库存服务。代价是原本一条 SQL 就能完成的关联查询,现在需要跨服务调用或数据冗余。因此,独立数据库不是“为了拆而拆”,而是用服务边界换取演进自由。

二、数据去中心化

数据去中心化是独立数据库的自然延伸。它强调没有全局的、唯一的事实来源数据库,每个服务只对自己领域内的数据负责。订单服务是订单状态的权威来源,库存服务是库存数量的权威来源。当订单服务需要展示商品名称时,它不应实时查询商品库,而应在下单时把商品名称、单价等快照冗余到订单记录中。

去中心化带来两个典型问题:跨服务查询和数据一致性。前者通常通过 API 组合、CQRS 读模型或数据同步解决;后者则无法再用本地事务保证,必须引入最终一致性。

三、最终一致性

最终一致性是指:系统在没有新的更新操作后,经过一段时间,所有副本或相关服务的数据会达到一致状态。它不要求任意时刻强一致,但要求最终收敛。例如用户下单后,订单服务立即写入订单,库存服务稍后扣减库存。在扣减完成前,订单已存在但库存尚未变化,这是一个中间状态;只要最终库存被正确扣减,就满足最终一致性。

实现最终一致性需要解决三个问题:可靠的事件发布、幂等的消费者、失败补偿。可靠发布常用“本地事务 + 发件箱表(Outbox)”模式:业务数据和事件记录写入同一个本地事务,再由后台进程把事件投递到消息队列。消费者必须幂等,因为消息可能重复投递。失败补偿则由 Saga 模式负责。

四、Saga 模式

Saga 把一个跨服务的长事务拆成一系列本地事务,每个本地事务对应一个服务操作,并为每个操作定义一个补偿操作。如果某一步失败,就按相反顺序执行前面已成功步骤的补偿操作,使系统回到业务上可接受的状态。

Saga 有两种协调方式:编排式(Choreography)由各服务监听事件自行触发下一步;编排器式(Orchestration)由一个中心协调器按顺序调用各服务。前者去中心化但链路难追踪,后者逻辑集中但协调器可能成为瓶颈。

以“创建订单”为例,一个简化的 Saga 如下:

步骤1 订单服务:创建订单(状态=PENDING)
步骤2 库存服务:扣减库存
步骤3 支付服务:发起支付
步骤4 订单服务:更新订单(状态=CONFIRMED)

若步骤3失败:
补偿3 支付服务:无需补偿(未成功)
补偿2 库存服务:回补库存
补偿1 订单服务:取消订单(状态=CANCELLED)

注意补偿不是“回滚”,而是业务上的反向操作。例如扣减库存的补偿是增加库存,而不是数据库回滚。补偿操作也必须幂等,因为补偿本身也可能重试。

五、事件驱动架构

事件驱动架构是 Saga 和最终一致性的技术底座。服务在完成本地事务后发布领域事件,如 OrderCreated、InventoryDeducted、PaymentFailed。其他服务订阅这些事件并做出反应。事件是“已经发生的事实”,用过去时命名,不可变,可持久化。

事件驱动架构的关键设计点包括:事件格式(通常包含事件 ID、类型、聚合 ID、时间戳、负载)、投递语义(至少一次,因此消费端必须幂等)、顺序性(同一聚合的事件应有序,可用分区键保证)、以及死信队列处理无法消费的消息。

事件驱动不等于“所有服务都直接订阅所有事件”。应控制事件扇出,避免一个事件被过多服务依赖,导致耦合重新上升。必要时使用事件转换或专用读模型。

六、四条原则之间的关系

这四条原则不是并列的,而是层层递进:独立数据库是边界基础,数据去中心化是边界内的数据归属规则,最终一致性是跨边界协作时必须接受的一致性模型,Saga 与事件驱动则是实现最终一致性的具体手段。理解这个层次关系,才能在后续设计订单、库存、支付等具体服务时做出正确取舍。

微服务数据架构:四条原则与实现手段 ① 每个服务独立数据库 私有访问 · 自治演进 · 无跨库外键 · 无共享表 订单库 / 库存库 / 支付库 各自独立,互不直连 ② 数据去中心化 无全局唯一事实源 · 各服务只负责本领域数据 · 跨服务用快照/读模型 订单快照商品名/单价 避免实时跨库 JOIN ③ 最终一致性 允许中间状态 · 无新更新后收敛 · 可靠发布 + 幂等消费 + 失败补偿 Outbox 表 + 消息队列 至少一次投递,消费幂等 ④ Saga 模式 + 事件驱动架构 本地事务序列 · 补偿操作 · 领域事件发布/订阅 · 编排或协同 创建订单 PENDING 扣减库存 InventoryDeducted 发起支付 PaymentRequested 确认订单 CONFIRMED 失败时反向补偿:回补库存 → 取消订单

把这张图记在脑中,后续设计具体服务时就可以反复自问:这个数据归谁所有?跨服务操作失败后如何补偿?事件是否可靠发布、消费是否幂等?回答清楚这些问题,微服务数据架构才算真正落地。