缓存一致性,缓存穿透,缓存雪崩,缓存击穿

缓存一致性问题

在分布式环境下,由于数据库和缓存无法保证原子性(操作 DB 和 操作 Cache 要么同时成功,要么同时失败 )同步更新,导致数据变更时极易出现**缓存中残留旧数据(脏读)**的现象。


解决方案:Cache Aside Pattern (旁路缓存模式)

读的时候先读缓存,没有则读库并回写;写的时候先更库,再删缓存。

用其他策略(如"先删缓存再更DB"或"先更缓存再更DB")都有一个共同问题是:

极易在并发场景下产生"脏数据",且无法自动恢复。


为什么是"删除"而不是"更新"缓存?
  • 节省资源:如果写操作很频繁但读操作很少,频繁更新缓存是浪费。

缓存穿透:

用户请求查询一个根本不存在的数据(例如 ID 为 -1 或随机乱码的 ID),此时:

  1. 缓存中自然没有该数据。
  2. 数据库中也没有该数据。

所以每次请求都会直接穿透缓存,直达数据库进行查询。

如果有恶意攻击者利用大量不存在的 Key 发起高并发请求,数据库将承受巨大压力,甚至导致宕机

解决方案
方案一:缓存空对象 (Cache Null Values) ------ 最常用,实现简单,占内存
  • 逻辑
    1. 当 DB 查询结果为空时,依然写入缓存
    2. Value 设为特殊值(如 null"" 或特定占位符)。
    3. 设置一个较短的过期时间(如 5 分钟),避免占用过多内存。
    4. 下次请求命中该 Key,发现是空值,直接返回,不再查 DB。
  • 优点:实现简单,代码侵入性小,能有效保护数据库。
  • 缺点
    • 占用额外内存空间(存储大量空 Key)。
    • 存在短暂的不一致窗口(如果 DB 中随后插入了该数据,需等待缓存过期才能读到)。
方案二:布隆过滤器 (Bloom Filter) ------ 高性能,省内存
  • 逻辑
    1. 在缓存前加一层布隆过滤器(一种概率型数据结构)。
    2. 将所有可能存在的 Key(如所有商品 ID)预先加载到过滤器中。
    3. 请求进来先问过滤器:"这个 Key 存在吗?"
      • 说不存在一定不存在 -> 直接拦截,返回错误,绝不查 DB
      • 说存在可能存在(有误判率)-> 继续查缓存/DB。
  • 优点
    • 内存占用极小,查询效率极高。
    • 从源头拦截非法请求,彻底保护数据库。
  • 缺点
    • 有误判率(可能把不存在的说成存在,但绝不会把存在的说成不存在)。
    • 维护成本:数据变更时需同步更新过滤器(通常通过异步消息或定时任务)。

实践中可以两者结合使用(布隆过滤器挡掉大部分非法请求,漏网的少量请求用缓存空对象兜底)


缓存雪崩

大量缓存 Key 在同一时间集中过期 ,或者缓存服务整体宕机

导致瞬间所有请求直接涌向数据库,造成 DB 压力激增甚至崩溃。

解决方案
  1. 随机过期时间 (最常用):
    • 在原定过期时间基础上,增加一个随机值(如 1-5 分钟)。
    • 效果:让 Key 分散过期,避免"集体自杀"。
  2. 高可用架构
    • 搭建 Redis 集群/哨兵模式,防止单点故障导致整体宕机。
  3. 限流与降级
    • 当检测到流量异常激增时,触发限流 (拒绝部分请求)或降级(返回默认值/缓存旧数据),保护数据库。
  4. 缓存预热
    • 在大促或活动前,提前将热点数据加载到缓存中,并设置不同的过期策略。
  5. 多级缓存:
    • 浏览器缓存->nginx缓存->微服务代码里自定义缓存操作->redis缓存->db缓存

缓存击穿 (Cache Breakdown)

某一个热点 Key (如爆款商品、突发新闻)在高并发 访问下突然过期,并且这个key重建业务较复杂,,不能快速更新

导致瞬间大量请求直接穿透缓存,直击数据库,造成 DB 瞬时压力过大。

解决方案

方案一:互斥锁 (Mutex Lock) ------ 强一致性,最常用,可用性相对差

  • 逻辑
    1. 发现缓存过期。
    2. 获取分布式锁(如 Redis setnx)。
    3. 只有拿到锁的线程去查 DB 并回写缓存。
    4. 其他很多线程没拿到锁就休眠重试或等待,直到缓存重建完成。
  • 优点:保证数据强一致性
  • 缺点:用户需等待

方案二:逻辑过期 (Logical Expiration) ------ 可用性强,一致性相对差

  • 逻辑
    1. 永不过期:Redis 中的 Key 不设物理过期时间(TTL = -1)。
    2. 内部标记 :在 Value 内部包含一个逻辑过期时间戳(如 expire_time)。
    3. 异步重建
      • 请求发现逻辑已过期。
      • 立即返回旧数据(保证用户体验)。
      • 同时开启一个独立线程去查 DB 更新缓存。
  • 优点:无需加锁,响应极快,系统可用性高。
  • 缺点:会短暂返回旧数据(弱一致性),消耗额外线程资源。

总结:

穿透 是查不存在的数据(DB也没有)

击穿单个热点Key过期

雪崩大量 Key同时过期或服务宕机。

相关推荐
清水白石0086 小时前
Python 弱引用深度解析——让缓存不再成为内存泄漏的温床
java·python·缓存
難釋懷6 小时前
Redis搭建哨兵集群
数据库·redis·缓存
blurblurblun7 小时前
Redis底层专题(1)------ sds字符串
数据库·redis·缓存
lxh011310 小时前
有时间限制的缓存
缓存
匀泪10 小时前
云原生(Redis配置)
数据库·redis·缓存
北京地铁1号线10 小时前
快手面试题:LRU缓存
缓存·lru
lqj_本人10 小时前
基于 openYuanrong 的生成式推荐缓存高可用方向验证实践
前端·vue.js·缓存
lhbian11 小时前
redis分页查询
数据库·redis·缓存
We་ct11 小时前
React 中的双缓存 Fiber 树机制
前端·react.js·缓存·前端框架·reactjs·fiber·缓存机制