4. 服务边界划分原则:微服务架构下的服务边界、数据所有权、缓存与数据库的职责分离
在微服务架构中,服务边界的划分直接决定了数据所有权、缓存策略以及数据库职责的清晰度。错误的边界划分会导致缓存穿透、数据不一致、跨服务事务泛滥等问题。本章从领域驱动设计(DDD)出发,结合缓存一致性场景,给出可落地的边界划分原则。
4.1 服务边界划分的核心依据
服务边界不应以技术分层(如“数据服务层”“业务逻辑层”)为依据,而应围绕业务能力与数据变更边界进行划分。以下是三个核心判断标准:
| 判断维度 | 说明 | 反例 |
|---|---|---|
| 业务聚合 | 一组强相关的业务实体和操作,具有内聚的变更原因 | 将“用户信息”和“用户订单”拆到两个服务,却频繁跨服务联表查询 |
| 数据所有权 | 每个服务拥有且仅拥有其业务数据的写入权限 | 多个服务直接操作同一张数据库表 |
| 缓存边界 | 缓存与数据库的职责分离:缓存只属于数据所有者服务 | 下游服务直接读取上游服务的缓存键 |
关键原则:服务边界 = 数据变更边界 = 缓存失效边界。三者必须对齐,否则缓存一致性无法保证。
4.2 数据所有权与缓存职责分离
数据所有权决定了哪个服务负责数据的写入、校验和缓存管理。缓存不应成为共享层,而应作为数据所有者服务的内部加速组件。下图展示了正确的职责分离结构:
4.3 缓存与数据库的职责分离细则
在微服务中,缓存与数据库的职责分离不是技术选型问题,而是架构契约问题。以下细则必须纳入服务设计规范:
- 数据库是唯一真相来源(Source of Truth):缓存中的数据必须能从数据库恢复,缓存丢失不影响业务最终一致性。
- 缓存只加速读操作:写操作必须经过数据库,缓存仅在写入后失效或更新。
- 禁止跨服务共享缓存实例:不同服务即使使用同一Redis集群,也应使用不同的key前缀或逻辑数据库,避免key冲突和意外失效。
- 缓存失效由数据所有者触发:当数据变更时,所有者服务负责发送缓存失效事件,消费者服务监听后自行处理本地缓存。
实践建议:在OpenAPI规范中,为每个数据所有者服务的写入接口显式声明“缓存失效契约”,例如在响应头中添加
X-Cache-Invalidate: order:123,让API消费者明确知道哪些缓存键需要清理。
4.4 服务边界错误模式与后果
以下表格总结了常见的服务边界错误划分及其对缓存一致性的影响:
| 错误模式 | 表现 | 缓存一致性后果 |
|---|---|---|
| 共享数据库表 | 多个服务直接读写同一张表 | 缓存失效逻辑混乱,一个服务更新后无法通知其他服务的缓存 |
| 缓存穿透服务边界 | 服务A直接读取服务B的缓存key | 服务B变更缓存结构或key时,服务A静默失效,产生脏数据 |
| 数据所有权模糊 | 两个服务都认为自己拥有某字段的写入权 | 缓存更新顺序不确定,导致缓存与数据库永久不一致 |
| 跨服务事务写入 | 一个API调用同时写入两个服务的数据库 | 部分成功时缓存无法原子失效,出现部分更新 |
4.5 服务边界演进:从单体到微服务的缓存策略迁移
架构演进过程中,服务边界会逐步细化。缓存策略需要同步演进,否则会成为瓶颈。下图展示了从单体到微服务拆分过程中缓存职责的变化:
4.6 边界划分检查清单
在OpenAPI设计和微服务拆分评审中,使用以下清单验证服务边界是否正确:
- 数据写入权唯一吗? 每个数据字段只有一个服务拥有写入权限。
- 缓存失效可追踪吗? 数据变更时,所有者服务能明确发出失效信号。
- 跨服务查询有缓存吗? 消费者服务是否缓存了其他服务的数据?如果是,缓存失效机制是否已实现?
- 数据库表被多个服务访问吗? 如果是,考虑拆分表或引入数据视图服务。
- 缓存key是否包含服务标识? 避免不同服务使用相同key前缀导致冲突。
常见陷阱:不要为了“性能优化”而让消费者服务直接读取所有者服务的缓存。这会导致缓存与数据库之间出现不可控的时间窗口不一致,且难以排查。正确的做法是通过API或事件获取数据,并在消费者侧建立只读缓存。