Technical Note

Redis 缓存一致性:不要迷信一种删除策略

从读写路径、延迟双删、消息补偿和可观测性四个角度,整理缓存一致性方案。

  • Redis
  • Cache
  • Java

缓存一致性没有银弹。真正要先问的是:这个业务允许多久不一致,不一致时是否影响资金、权限或核心决策。

如果只是商品详情、配置项、文章信息,短暂不一致通常可以接受;如果是账户余额、权限关系、订单状态,就不能只靠“删缓存”碰运气。

我的判断: 缓存一致性不是选一个“标准答案”,而是把读写时序、失败补偿和用户可感知影响讲清楚。业务越关键,越不能只靠缓存删除成功。

更新数据库后删除缓存

这是最常见的策略。它的优势是简单,读请求缓存未命中时自然回源数据库。问题在于删除失败怎么办、主从延迟怎么办、并发读写交错怎么办。

所以生产里通常要加补偿:删除失败记录日志或消息,后台任务重试删除。

延迟双删适合兜底

延迟双删的思路是:更新前删一次缓存,数据库提交后再延迟删一次。它能缓解并发读把旧值写回缓存的问题,但延迟时间不好拍脑袋,要结合数据库提交时间、主从同步延迟和业务读频率。

缓存不是唯一防线

热点 Key、缓存穿透、击穿、雪崩都要一起考虑。空值缓存、互斥锁、逻辑过期、限流、降级,是缓存系统的配套工程。

观测指标

至少要看缓存命中率、回源数据库 QPS、Redis 慢查询、热点 Key、删除失败次数和补偿任务积压。没有观测的缓存一致性,只是“看起来能跑”。

实际场景:标签历史版本和看板预计算的缓存边界

BI 平台里有一类数据特别适合缓存,但也特别容易出一致性问题:标签历史版本、分群计算结果、看板预计算结果。它们不是简单的配置项,而是“某个时间点、某套查询条件、某个项目下”的计算结果。如果缓存 key 少了版本或时间窗口,用户就可能看到旧数据;如果更新策略太激进,又会导致每次查询都回源计算,失去缓存价值。

以标签历史版本为例,用户可能查询某个标签在指定日期的值内容,也可能批量重新计算历史版本。缓存 key 至少要包含项目、标签、版本日期、查询条件和分页信息。重新计算某个日期后,不能只删 tagId 级别的缓存,因为同一个标签不同日期仍然有效;也不能不删,否则页面继续命中旧版本。更稳的方式是把版本日期作为 key 的一部分,并在历史版本状态更新后只清理受影响窗口。

看板预计算也类似。看板组件的查询条件、时间范围、是否实时查询、用户权限范围都会影响结果。如果用户勾选“实时查询”,就应该跳过预计算缓存;如果是固定看板访问,就可以命中预计算结果。这里缓存一致性的关键不是“删不删 Redis”,而是先判断这次请求到底应该读缓存、读哪个缓存、缓存是否仍然代表当前配置。

缓存更新还要考虑异步任务。比如预计算任务正在跑,用户同时修改了看板配置。此时旧任务结果不能覆盖新配置结果。可以给配置加版本号,任务开始时记录版本,写入缓存前再次校验版本;如果版本不一致,就丢弃旧任务结果。这样即使异步任务晚返回,也不会把页面带回旧状态。

观测上,除了 Redis 命中率,还要看 “缓存命中新鲜度”。比如命中的是哪个版本、缓存生成时间、对应任务 ID、是否来自预计算。用户反馈数据不对时,开发应该能快速判断是缓存旧了、任务没跑完、查询条件变了,还是底层数据本来就没更新。缓存一致性的成熟度,就体现在这些问题能不能被解释清楚。

参考资料