跳过导航

Redis 与数据库双写一致性为什么没有银弹?

约 4 分钟...次浏览
专栏Redis 深度专题第 7 篇

数据库是权威数据,Redis 是副本。一次业务更新要跨越两个独立系统,在没有分布式事务时总会存在失败和并发窗口。所谓“最佳方案”只能在一致性、可用性、复杂度和成本之间取舍。

1. 先淘汰错误直觉:先更新数据库,再更新缓存

两个并发写可能乱序:

请求 A 更新数据库为 1(暂停)
请求 B 更新数据库为 2 → 更新缓存为 2
请求 A 恢复 → 更新缓存为 1

数据库是 2,缓存却回退为 1。更新缓存还会为无人读取的数据做无效工作,并需要精确复制数据库事务后的最终值。

2. Cache Aside:写库后删缓存

最常见模式:

@Transactional
public void updateProduct(Product product) {
    repository.update(product);
    // 注意:这里若事务尚未提交,删除时机仍需设计
}

// 提交成功后删除 product:<id>

读请求缓存 miss 时查库并回填。删除比更新更稳健,因为下一次读取会从权威源重建;但仍有窗口:数据库提交成功、删除 Redis 失败,旧缓存会继续存在。

因此删除必须可重试,且重试信息不能只存在当前进程内存。

3. 读写并发仍可能产生旧值回填

读请求 R:缓存 miss,读到数据库旧值 1(暂停)
写请求 W:数据库改为 2,删除缓存
读请求 R:恢复,把旧值 1 写入缓存

这个窗口通常较窄,却真实存在。缓解手段:

  • 缩短缓存 TTL,限制不一致时间;
  • 延迟一小段时间再次删除(延迟双删);
  • 缓存值携带数据库版本,拒绝旧版本覆盖新版本;
  • 对极关键读直接访问权威库或使用一致性更强的架构。

延迟双删不是数学证明。延迟多久取决于读路径耗时,进程崩溃也可能让第二次删除丢失,必须通过可靠任务执行。

4. 事务提交之后再失效

若在数据库事务提交前删除缓存,其他线程可能立即 miss、读到尚未提交的旧数据并回填。Spring 中可注册事务提交后的回调,或使用事务事件:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void onProductChanged(ProductChanged event) {
    cache.delete(event.key());
}

但应用在提交后、发送事件前崩溃仍会丢失操作。进程内 after-commit 只解决时序,不解决可靠投递。

5. Outbox:把“需要失效”写入同一事务

BEGIN;
UPDATE product SET price = ?, version = version + 1 WHERE id = ?;
INSERT INTO outbox(event_id, aggregate_id, type, payload)
VALUES (?, ?, 'PRODUCT_CHANGED', ?);
COMMIT;

后台发布器读取 outbox,删除缓存或发送消息,成功后标记完成。数据库更新和事件记录在同一事务,避免提交成功却完全丢失失效意图。发布可能重复,所以删除操作和消费者必须幂等。

另一种方式是订阅数据库变更日志(CDC/binlog)并异步失效缓存。它减少业务侵入,但增加基础设施、延迟和事件顺序治理。

6. TTL 是最后保险,不是主要协议

所有缓存设置有限 TTL,可以让偶发失效失败最终自愈。但 TTL 太长会延长脏读,太短则降低命中率并增加数据库压力。应根据业务可接受陈旧时间定义,而不是全站统一 30 分钟。

金融余额、权限撤销等数据可能无法接受缓存旧值,应该改变读取架构,不能仅把 TTL 调成一秒后宣称一致。

7. 监控与验证

  • 缓存删除失败次数和重试队列积压;
  • outbox 最老未处理事件年龄;
  • 抽样比较缓存版本与数据库版本;
  • 缓存命中率、回源率和重建延迟;
  • 故障注入:Redis 超时、应用在提交后崩溃、消息重复与乱序;
  • 修复工具:按 ID 或版本批量失效,而非线上 FLUSHDB

8. 检查清单

  • 是否明确数据库才是权威源?
  • 写路径是否在提交成功后删除缓存?
  • 删除失败是否有持久化重试?
  • 是否处理旧读回填,可否使用版本号?
  • 所有缓存是否有符合业务的 TTL?
  • 极关键数据是否应该绕开普通 Cache Aside?

缓存一致性没有银弹,因为跨系统原子性没有凭空出现。多数业务可采用“提交后删除 + 可靠重试/Outbox + TTL 兜底 + 版本保护”,并明确允许多长时间的最终一致。

分享:
文章作者:狼码纪
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。文章可能参考了其他优秀文章,如有侵权请联系删除。