数据边界与数据所有权
微服务架构中最常见的错误之一,是将数据库当作“共享总线”来对待。当多个服务直接读写同一张表或共享同一个数据库实例时,组件之间的耗时耦合就从代码层蔓延到了数据层。本章将围绕数据所有权的核心原则,澄清“每个组件独立数据存储”的真实含义,并给出共享数据与去中心化存储之间的权衡边界。
核心原则:数据归谁管,谁说了算
数据所有权(Data Ownership)规定:一个业务实体(如订单、用户、商品)只能由唯一的一个服务负责写入和更新。其他服务只能通过该服务提供的 API 或消息事件来间接获取数据。这一原则直接决定了数据存储的边界。
| 对比维度 | 中心化存储(反模式) | 去中心化存储(推荐) |
|---|---|---|
| 数据库归属 | 多个服务共享同一 Schema | 每个服务拥有独立 DB/表空间 |
| 写入权限 | 多个服务可写同一表 | 仅所有者服务可写 |
| 数据一致性 | 强一致(依赖数据库锁) | 最终一致(依赖事件驱动) |
| 扩展性 | 受单库瓶颈限制 | 可独立扩缩每个服务存储 |
| 变更风险 | Schema 变更需协调所有方 | 仅影响所有者服务 |
典型反模式:在拆分为订单、支付、库存三个服务后,仍然将三张表放在同一个 MySQL 数据库中,且三个服务都持有直连该库的凭据。这种“物理拆分、逻辑耦合”的做法完全抵消了微服务在独立部署上的优势。
数据同步策略
一旦确立了独立存储,跨服务的数据同步就成为必须面对的工程问题。下面列举三种主流策略及其适用场景。
- 事件驱动异步同步(推荐):所有者服务在业务写入后发布领域事件,消费方服务监听并更新自己的本地副本。适合写少读多、对延迟不敏感的业务。
- API 查询回源:消费方在需要数据时直接调用所有者服务的 API 获取最新状态,不存本地副本。适合数据实时性要求极高的场景,但会增加调用链路延迟。
- 双向同步 / CRDT:多个服务各自维护数据片段,通过无冲突复制数据类型(CRDT)或分布式共识算法(如 Raft)保持最终一致。适用于离线优先或多活场景,工程复杂度较高。
选型建议:优先采用“事件驱动 + 本地副本”的组合。本地副本让消费方服务能够独立运行,不依赖上游的可用性;而事件机制保证了数据所有权的单一写入端不被打破。
共享数据 vs. 去中心化:判断标准
并非所有数据都应该去中心化。配置数据、不可变的参考数据(如城市编码表)或强一致的账本项目,共享存储反而更简单。判断是否应该去中心化,可以使用以下三问测试:
- 写入所有权:该数据是否只有一个服务有明确的写入责任?—— 是则去中心化
- 查询实时性:消费方是否需要读取到“刚刚提交”的数据?—— 是则考虑共享或 API 回源
- 变更影响面:该数据的 Schema 变更是否需要同步通知多个团队?—— 需要则去中心化
下图为数据边界划分的决策流程图:
实践建议:如何逐步落地
- 识别数据所有者:为每个业务实体指定唯一写入服务,画出数据所有权矩阵。
- 拆分存储:按服务独立创建数据库 Schema 或物理数据库,迁移存量数据。
- 定义同步契约:采用异步消息(Kafka、RabbitMQ)或 gRPC 流,明确事件 Schema 和一致性级别。
- 设置同步降级:当消息队列积压或上游不可达时,消费方应能基于本地副本提供降级服务。
- 监控数据对账:周期性对比所有者与消费方数据,发现差异后自动或手工修正。
验收标准
- 不存在两个服务直连同一个数据库实例进行写操作。
- 任意服务的数据 Schema 变更,不会导致其他服务的数据表变更。
- 在消息中间件完全故障的情况下,各服务仍能基于本地缓存提供降级响应(数据可能延迟)。
- 数据同步延迟可观测、可配置,且有明确的 SLA(例如:订单状态同步不超过 5 秒)。