3. 数据库缓存一致性:缓存与数据库双写一致性策略
在数据库与缓存并存的架构中,保证两者数据一致是核心挑战。本章聚焦三种主流双写一致性策略:Cache-Aside、Read-Through 和 Write-Through,分析其工作原理、适用场景及在契约测试中的校验要点。
3.1 一致性问题的根源
缓存与数据库是两套独立的存储系统,写入顺序、并发操作、网络延迟都可能导致数据不一致。常见问题包括:
- 脏读:缓存中残留旧数据,数据库已更新。
- 缓存穿透:大量请求绕过缓存直接访问数据库。
- 缓存雪崩:缓存大面积失效,数据库压力骤增。
- 并发覆盖:多个线程同时写入,导致数据错乱。
契约测试视角:在回归冒烟阶段,缓存API的响应格式必须明确标识数据来源(缓存/数据库)和时间戳,以便校验一致性策略是否生效。
3.2 Cache-Aside 策略
Cache-Aside(旁路缓存)是最常用的策略,应用程序直接管理缓存与数据库的读写操作。
3.2.1 读操作流程
- 应用先查询缓存。
- 缓存命中,直接返回数据。
- 缓存未命中,查询数据库。
- 将数据库结果写入缓存,并设置过期时间。
- 返回数据给调用方。
3.2.2 写操作流程
- 应用更新数据库。
- 删除或更新缓存中的对应数据。
常见陷阱:先更新数据库再删除缓存,存在短暂的不一致窗口。推荐使用“延迟双删”策略:先删除缓存,再更新数据库,最后延迟一段时间再次删除缓存。
3.3 Read/Write Through 策略
Read/Write Through(读写穿透)将缓存层作为数据访问的唯一入口,应用不直接操作数据库。
3.3.1 Read-Through 流程
- 应用只与缓存交互。
- 缓存未命中时,缓存组件自动从数据库加载数据。
- 加载完成后,缓存返回数据给应用。
3.3.2 Write-Through 流程
- 应用写入数据到缓存。
- 缓存组件同步将数据写入数据库。
- 写入成功后,缓存返回确认给应用。
关键区别:Cache-Aside 由应用控制一致性逻辑,灵活性高;Read/Write Through 由缓存层封装一致性逻辑,对应用透明,但实现复杂度转移到缓存中间件。
3.4 策略对比表
| 特性 | Cache-Aside | Read-Through | Write-Through |
|---|---|---|---|
| 应用复杂度 | 高(需自行管理缓存逻辑) | 低(缓存层处理读) | 低(缓存层处理写) |
| 一致性保证 | 弱(需额外机制) | 中等(读路径一致) | 强(写路径同步) |
| 性能影响 | 读快,写有缓存删除开销 | 首次读慢,后续快 | 写延迟增加(同步写库) |
| 适用场景 | 读多写少,可接受短暂不一致 | 读密集,数据加载模式固定 | 写密集,要求强一致性 |
| 契约测试关注点 | 缓存删除后响应格式 | 缓存未命中时回源响应 | 写入后缓存与数据库同步状态 |
3.5 双写一致性流程对比图
下图展示了 Cache-Aside 与 Write-Through 在写操作时的核心差异:
3.6 契约测试中的一致性校验
在回归冒烟测试中,缓存API的响应格式必须包含以下字段以支持一致性校验:
{
"status": "success",
"data": {
"id": "user_123",
"name": "张三",
"updated_at": "2025-03-21T10:30:00Z"
},
"meta": {
"source": "cache", // 数据来源:cache / database
"cache_hit": true, // 是否缓存命中
"consistency_strategy": "cache-aside", // 当前策略
"ttl_remaining": 120 // 缓存剩余生存时间(秒)
}
}
契约断言要点:
- 写操作后,缓存API应返回
source: "database"或明确标识缓存已失效。 - 读操作时,若缓存命中,
cache_hit必须为true,且source为"cache"。 - Write-Through 策略下,写操作响应中应包含
db_write_confirmed: true字段。
3.7 策略选择建议
| 业务场景 | 推荐策略 | 理由 |
|---|---|---|
| 用户信息读取(读多写少) | Cache-Aside | 灵活,可接受短暂不一致 |
| 商品库存(写密集) | Write-Through | 需要强一致性,避免超卖 |
| 配置信息(读密集,更新少) | Read-Through | 缓存层自动回源,应用无感知 |
| 会话数据(读写均衡) | Cache-Aside + 延迟双删 | 平衡一致性与性能 |
回归冒烟最佳实践:在契约测试中,为每种策略定义独立的响应格式契约。例如,Cache-Aside 的写响应应包含
cache_invalidated: true,而 Write-Through 的写响应应包含 db_sync_status: "synced"。这样在回归测试中,可以快速定位一致性策略是否被正确执行。