数据边界与数据所有权

微服务架构中最常见的错误之一,是将数据库当作“共享总线”来对待。当多个服务直接读写同一张表或共享同一个数据库实例时,组件之间的耗时耦合就从代码层蔓延到了数据层。本章将围绕数据所有权的核心原则,澄清“每个组件独立数据存储”的真实含义,并给出共享数据与去中心化存储之间的权衡边界。

核心原则:数据归谁管,谁说了算

数据所有权(Data Ownership)规定:一个业务实体(如订单、用户、商品)只能由唯一的一个服务负责写入和更新。其他服务只能通过该服务提供的 API 或消息事件来间接获取数据。这一原则直接决定了数据存储的边界。

对比维度 中心化存储(反模式) 去中心化存储(推荐)
数据库归属 多个服务共享同一 Schema 每个服务拥有独立 DB/表空间
写入权限 多个服务可写同一表 仅所有者服务可写
数据一致性 强一致(依赖数据库锁) 最终一致(依赖事件驱动)
扩展性 受单库瓶颈限制 可独立扩缩每个服务存储
变更风险 Schema 变更需协调所有方 仅影响所有者服务
典型反模式:在拆分为订单、支付、库存三个服务后,仍然将三张表放在同一个 MySQL 数据库中,且三个服务都持有直连该库的凭据。这种“物理拆分、逻辑耦合”的做法完全抵消了微服务在独立部署上的优势。

数据同步策略

一旦确立了独立存储,跨服务的数据同步就成为必须面对的工程问题。下面列举三种主流策略及其适用场景。

  1. 事件驱动异步同步(推荐):所有者服务在业务写入后发布领域事件,消费方服务监听并更新自己的本地副本。适合写少读多、对延迟不敏感的业务。
  2. API 查询回源:消费方在需要数据时直接调用所有者服务的 API 获取最新状态,不存本地副本。适合数据实时性要求极高的场景,但会增加调用链路延迟。
  3. 双向同步 / CRDT:多个服务各自维护数据片段,通过无冲突复制数据类型(CRDT)或分布式共识算法(如 Raft)保持最终一致。适用于离线优先或多活场景,工程复杂度较高。
选型建议:优先采用“事件驱动 + 本地副本”的组合。本地副本让消费方服务能够独立运行,不依赖上游的可用性;而事件机制保证了数据所有权的单一写入端不被打破。

共享数据 vs. 去中心化:判断标准

并非所有数据都应该去中心化。配置数据、不可变的参考数据(如城市编码表)或强一致的账本项目,共享存储反而更简单。判断是否应该去中心化,可以使用以下三问测试:

  • 写入所有权:该数据是否只有一个服务有明确的写入责任?—— 是则去中心化
  • 查询实时性:消费方是否需要读取到“刚刚提交”的数据?—— 是则考虑共享或 API 回源
  • 变更影响面:该数据的 Schema 变更是否需要同步通知多个团队?—— 需要则去中心化

下图为数据边界划分的决策流程图:

数据边界与数据所有权决策流程 业务数据实体 写入方唯一? 是 去中心化:独立库 + 事件同步 否 实时查询? 是 共享缓存 / API 回源 否 去中心化:本地副本 + 延时同步 决策说明 • “写入方唯一”是去中心化的首要条件。若多个服务可写同一数据,则必须共享存储。 • “实时查询”指消费方是否需要看到严格的最新值(秒级以内)。是则优先 API 回源或共享缓存。 • 避免在去中心化方案中使用分布式事务;优先接受最终一致性,用补偿机制兜底。

实践建议:如何逐步落地

  1. 识别数据所有者:为每个业务实体指定唯一写入服务,画出数据所有权矩阵。
  2. 拆分存储:按服务独立创建数据库 Schema 或物理数据库,迁移存量数据。
  3. 定义同步契约:采用异步消息(Kafka、RabbitMQ)或 gRPC 流,明确事件 Schema 和一致性级别。
  4. 设置同步降级:当消息队列积压或上游不可达时,消费方应能基于本地副本提供降级服务。
  5. 监控数据对账:周期性对比所有者与消费方数据,发现差异后自动或手工修正。

验收标准

  • 不存在两个服务直连同一个数据库实例进行写操作。
  • 任意服务的数据 Schema 变更,不会导致其他服务的数据表变更。
  • 在消息中间件完全故障的情况下,各服务仍能基于本地缓存提供降级响应(数据可能延迟)。
  • 数据同步延迟可观测、可配置,且有明确的 SLA(例如:订单状态同步不超过 5 秒)。