← 返回题目列表

Cache Aside 模式是什么?读写缓存时有哪些坑?

高频 中等 第 1 / 36 题 更新于 2026/07/29
RedisCache Aside缓存一致性缓存设计

简化版

Cache Aside 是最常见的旁路缓存模式:读时先查缓存,未命中再查数据库并回填;写时先更新数据库,再删除缓存。它简单通用,但要处理缓存击穿、并发回填、删除失败、延迟双删、缓存雪崩和脏数据窗口。

详细版

Cache Aside 的读流程是:

读请求 -> 查缓存
命中 -> 返回
未命中 -> 查数据库 -> 写缓存 -> 返回

写流程常见是:

写请求 -> 更新数据库 -> 删除缓存

为什么不是更新缓存?因为缓存可能有复杂聚合、并发写覆盖、部分字段更新等问题,删除后下次读再回填更简单。

主要风险包括缓存未命中并发打 DB、缓存删除失败、读写并发导致短暂旧值、热点 key 失效、缓存无 TTL 或 TTL 集中到期。面试要说明这是最终一致方案,不是强一致事务。

完整版教学

一、Cache Aside 解决什么问题

Cache Aside 又叫旁路缓存,应用代码同时访问缓存和数据库。缓存不主动和数据库绑定,应用决定什么时候读缓存、回填缓存、删除缓存。

它适合读多写少的业务。缓存承担高频读,数据库保存权威数据。这样既能降低数据库压力,又保持数据源清晰。

记忆钩子:Cache Aside 的核心是“读旁路回填,写数据库删缓存”。

二、读流程为什么要回填

读请求先查 Redis,如果命中直接返回;如果没命中,再查数据库,把结果写入 Redis,然后返回给用户。

GET cache
   |
命中 -> 返回
   |
未命中 -> SELECT DB -> SET cache TTL -> 返回

回填时要设置 TTL,避免缓存永久占用内存,也能让异常情况下的数据最终自然过期。对于不存在的数据,也可以缓存空值短 TTL,防止缓存穿透。

三、写流程为什么常用“更新 DB 后删缓存”

写入时常见做法是先更新数据库,再删除缓存。下次读缓存未命中,会从数据库加载新值。

如果写缓存而不是删缓存,会遇到更多问题:缓存结构可能不是数据库单行,可能是聚合结果;并发写时旧请求可能覆盖新缓存;写数据库成功但写缓存失败也会造成不一致。

写法风险
更新 DB 后更新缓存并发覆盖、聚合难维护
更新 DB 后删除缓存短暂不一致但更简单
先删缓存后更新 DB并发读可能回填旧值

四、读写并发下为什么仍可能脏

即使采用“更新 DB 后删缓存”,也存在短暂窗口。比如读请求先查缓存未命中,随后查到旧 DB 值;写请求更新 DB 并删除缓存;读请求最后把旧值回填进缓存。

T1 读:缓存未命中
T2 读:查询 DB 旧值
T3 写:更新 DB 新值
T4 写:删除缓存
T5 读:把旧值写回缓存

这个场景概率不高但真实存在。可以用延迟双删、版本号、较短 TTL、互斥回填、消息队列重删等方式降低风险。

五、删除失败怎么办

更新数据库成功后,如果删除缓存失败,就会留下旧缓存。工程上不能只写一行删除命令就结束,要有重试和补偿。

常见方案包括同步重试、异步重试、消息队列、订阅 binlog 删除缓存。强一致要求越高,方案越复杂。

更新 DB 成功
   |
删除缓存失败
   |
记录重试任务 / MQ / binlog 消费
   |
最终删除缓存

如果业务能接受短暂不一致,TTL 也是兜底;如果不能接受,要重新评估是否适合缓存。

六、并发回填和击穿问题

热点 key 过期后,大量请求同时未命中,会一起打到数据库,这就是击穿。Cache Aside 必须配合互斥锁、singleflight、逻辑过期或热点预热。

假设热点 key 每秒 5000 QPS,缓存过期瞬间如果 1000 个请求同时查 DB,数据库可能被压垮。加互斥回填后,只有一个请求查 DB,其他请求等待或返回旧值。

方案适用
互斥锁回填强一致读较重要
逻辑过期可返回短暂旧值
热点预热明确热点数据

七、TTL 设计别太机械

TTL 不能所有 key 都设置同一时间,否则容易集中失效形成雪崩。可以给 TTL 加随机抖动,比如基础 30 分钟,加随机 0 到 5 分钟。

TTL = 1800 + random(0, 300)

缓存空值也要设置较短 TTL,避免不存在 key 被反复打到数据库。不同业务数据要按变化频率、热点程度和一致性要求设置不同 TTL。

八、常见误区与追问

  • 误区:Cache Aside 能保证强一致。 它通常是最终一致方案,存在短暂不一致窗口。
  • 误区:写数据库后更新缓存一定更好。 并发覆盖和聚合缓存维护会让更新缓存更容易出错。
  • 误区:删除缓存失败靠 TTL 就够了。 TTL 是兜底,核心数据仍要有重试或补偿。
  • 追问:为什么不先删缓存再写数据库? 并发读可能在 DB 更新前回填旧值,导致旧缓存存活到 TTL。
  • 追问:延迟双删解决什么? 降低读写并发下旧值回填缓存的概率。
  • 追问:热点 key 过期怎么处理? 互斥回填、逻辑过期、预热和 TTL 抖动组合使用。

九、加强记忆

记住“读缓存,不中查库回填;写数据库,成功后删缓存”。这是 Cache Aside 的骨架。

真正能拉开差距的是补上四个坑:并发回填、删除失败、短暂脏读、TTL 集中失效。能讲到这四个点,答案就不像模板。