首页/数据/数据库速查/Cache Aside 缓存模式(旁路缓存)
Redis 运维与场景124 条

Cache Aside 缓存模式(旁路缓存)

最主流的缓存用法:读时先查缓存、未命中查库回填;写时先写库、再删除缓存。删除而非更新缓存是避免并发写覆盖的关键。

数据库速查数据更新于 2026-10-13

Cache Aside 是什么

旁路缓存是最主流的缓存用法:读时先查缓存,未命中再查数据库并回填;写时先写库、再删除缓存。之所以删缓存而不是更新缓存,是因为并发写入时「更新库」的顺序与「更新缓存」的顺序可能交错,删除能让下次读自然加载到最新值。它提供的是最终一致性,TTL 是兜底。

基本信息

读路径缓存优先,未命中查库并回填,通常带 TTL
写路径先写数据库,再删除缓存键
一致性最终一致,靠 TTL 收敛
TTL 建议按数据变化频率设 5 分钟到数小时
失效兜底删缓存失败要有重试或消息补偿

典型使用场景

  • 商品详情、用户资料等读多写少数据:命中率通常能到 90% 以上
  • 热点列表首页数据:用 TTL 控制刷新频率,扛住突发流量
  • 配置类低频变更数据:变更时主动删键,平时全走缓存
  • 作为多级缓存的一级:本地缓存之上再加 Redis 共享层
  • 写扩散场景的反例判断:数据写多读少时缓存收益有限,先算命中率
  • 跨服务共享读:多个服务读同一份数据,用统一 key 规则避免重复回源

读写伪代码

读写伪代码
# 读:缓存优先
def get_user(uid):
    v = redis.get('user:%s' % uid)          # 1) 先查缓存
    if v is None:                            # 2) 未命中(不是空值)
        row = db.query(uid)                  # 3) 回源查库
        redis.set('user:%s' % uid, json.dumps(row), ex=300)  # 4) 回填并设 TTL
        return row
    return json.loads(v)

# 写:先写库,再删缓存
def update_user(uid, data):
    db.update(uid, data)                     # 1) 先落库
def _del(uid):
    redis.delete('user:%s' % uid)            # 2) 再删缓存,不是更新缓存
    time.sleep(0.5)
    redis.delete('user:%s' % uid)            # 3) 延迟双删,压低回填竞态影响

写策略对照

写策略风险结论
先写库再删缓存删缓存失败会残留旧值直到 TTL 到期主流做法,需保证删除有重试
先删缓存再写库删除后回源读到旧库值,脏数据被回填并发下明显更脏,不推荐
直接更新缓存并发写顺序交错,缓存值可能永久错除计数器等特殊场景外不推荐
缓存加过期时间过期瞬间并发回源压力上升必须设置,是最终一致性的底线
为什么删缓存而不是更新缓存:并发写时先写库 A 后写库 B 与缓存的更新顺序可能交错,删除让下次读自然加载最新值。
删除缓存失败要有补偿:可写进消息队列重试,或依赖 TTL 收敛。对强一致要求的数据(如账户余额)不要走旁路缓存,直接在数据库上做。

易错点

  • 只更新缓存不删缓存,并发写导致缓存长期停留在旧值
  • 回源回填时不设 TTL,脏数据变成永久数据,只能人工清理
  • 缓存与数据库双写失败(删缓存报错)却无重试,用户看到旧数据直到过期
  • 把 null 当未命中,不存在的键每次都打到数据库,形成缓存穿透
  • 缓存里存大对象:单键几十 MB,回源与删除都造成延迟毛刺

数据来源与参考

  • Redis 官方文档: 缓存模式
  • 缓存一致性实践(Cache Aside)

相关工具

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌