Cache Aside 是什么
Cache Aside(旁路缓存)是最常见的缓存模式:缓存不参与数据库写入,应用代码自己决定先读谁、先写谁。读路径先查缓存,未命中就回源数据库并把结果写回缓存;写路径先落库,再删除缓存而不是更新缓存。之所以删而不改,是因为并发更新时「改缓存」会留下旧值覆盖新值的窗口,而删除只是让下一次读自然回源,代价是一次缓存穿透。
基本信息
| 读顺序 | GET 缓存命中即返回;未命中查库后 SET 缓存并设置 TTL |
|---|---|
| 写顺序 | 先 UPDATE 数据库,再 DEL 缓存,顺序不可颠倒 |
| 过期策略 | 写回缓存时一律带 TTL,避免脏数据长期驻留 |
| 兜底手段 | 删除失败重试、延迟双删、binlog 订阅补偿 |
| 一致级别 | 最终一致,业务需接受短暂不一致窗口 |
典型使用场景
- 读多写少的详情页、商品、用户资料等对象缓存,绝大多数请求都能在缓存命中
- 跨服务共享的热点数据,多个应用读同一份缓存而只有一个写入口
- 需要缓存但不愿引入复杂同步机制的改造项目,旁路缓存对现有代码侵入最小
- 写入频率低、允许秒级延迟的配置类数据
- 与消息队列配合做缓存失效广播,写库后异步通知各应用节点清理本地缓存
读路径与写路径
读路径与写路径
# 读路径:先缓存,未命中回源并写回
redis-cli GET user:1001
redis-cli SET user:1001 '{"name":"alice"}' EX 600 # 回源后写回,务必带 TTL
# 写路径:先更新库,再删缓存
# UPDATE users SET name='bob' WHERE id=1001;
redis-cli DEL user:1001
redis-cli TTL user:1001 # 确认已删除,返回 -2删缓存 vs 更新缓存
| 做法 | 并发风险 | 结论 |
|---|---|---|
| 写库后删缓存 | 下次读回源,窗口内有并发可能读到旧值 | 常规推荐 |
| 写库后更新缓存 | 两个写者交错可能把旧值写回缓存 | 不推荐 |
| 先删缓存再写库 | 删除与写库之间的读请求会把旧值写回缓存 | 经典脏数据来源 |
| 延迟双删 | 先删、写库、延迟再删一次 | 降低但不消除风险 |
延迟双删为什么要「延迟」:写入数据库后,此前未命中的读请求可能已经把旧值重新塞回缓存。延迟一段时间(常见几百毫秒到数秒)再删一次,可以覆盖这类回填,延迟时间按回源耗时上限估算,而不是随意取值。
缓存与数据库无法做到强一致:删除操作可能因为网络抖动失败,也可能在主从切换窗口丢失。对一致性敏感的关键路径应让 redis 之外的机制兜底,例如订阅数据库变更日志做最终补偿,或对关键写入直接绕过缓存。
写回缓存时忘记设置 TTL,是脏数据永久驻留的最常见原因。即使删除逻辑执行失败,只要 TTL 存在,数据也会在有限时间内自愈。