essay

缓存常见问题

#redis

缓存雪崩、缓存穿透、缓存击穿详解

缓存雪崩、缓存穿透、缓存击穿,都是高并发系统里常见的缓存问题。它们都会导致请求绕过缓存、直接打到数据库,从而引发数据库压力过大,甚至系统故障。


一、三者分别是什么意思

1. 缓存穿透

缓存穿透是指:请求的数据根本不存在,所以缓存中没有,数据库中也没有,请求每次都会落到数据库。

比如反复请求一个不存在的商品 ID:

  • productId = -1
  • productId = 999999999

由于这个数据本来就不存在,所以无论查多少次,缓存和数据库都查不到,最终每次都要访问数据库。


2. 缓存击穿

缓存击穿是指:某个热点 key 在失效的一瞬间,大量并发请求同时到来,因为缓存没有命中,这些请求会一起访问数据库。

比如:

  • 首页热点商品
  • 热门文章详情
  • 秒杀商品库存
  • 明星用户主页

这些数据访问量很高,如果它们的缓存刚好过期,就可能在短时间内把数据库打爆。


3. 缓存雪崩

缓存雪崩是指:大量缓存 key 在同一时间失效,或者缓存服务整体不可用,导致原本应该由缓存承接的请求,全部打到数据库。

比如:

  • 一批缓存统一设置了相同过期时间
  • Redis 整体宕机
  • 缓存集群不可用
  • 系统重启导致大量缓存丢失

这类问题影响范围最大,严重时可能引发系统级联故障。


二、三者的核心区别

问题类型 含义 数据是否存在 影响范围 典型场景
缓存穿透 查一个根本不存在的数据,缓存和数据库都没有 不存在 某类不存在的请求 恶意攻击、非法参数、异常请求
缓存击穿 某个热点 key 失效,大量并发同时访问数据库 存在 单个热点 key 热门商品、热点文章、秒杀库存
缓存雪崩 大量 key 同时失效,或缓存整体挂掉 通常存在 大批 key 或整个缓存层 大量缓存同一时刻过期、Redis 宕机

三、缓存穿透的原因与解决方案

1. 原因

缓存穿透常见原因包括:

  • 恶意请求不存在的 key
  • 参数非法,业务没有做好校验
  • 爬虫或机器人频繁访问异常数据
  • 用户请求的数据本来就不存在

2. 解决方案

方案一:缓存空值

当数据库查询结果为空时,也把这个空结果缓存起来,比如缓存 null 或特殊标记值。

示例:

  • product:10001
  • 数据库中不存在
  • Redis 中写入:product:10001 -> null
  • 设置一个较短 TTL,比如 60 秒或 300 秒

优点:

  • 下次再请求同一个不存在的数据时,直接从缓存返回
  • 能有效减少重复无效查询

注意:

  • 空值缓存时间不要太长
  • 否则后续如果该数据被新建,可能短时间读不到最新值

方案二:布隆过滤器

在系统中维护一个布隆过滤器,把所有可能存在的 key 放进去。请求到来时先判断:

  • 如果布隆过滤器判定“一定不存在”,直接返回
  • 如果布隆过滤器判定“可能存在”,再继续查缓存和数据库

布隆过滤器本质上做两件事:
1.准备一个很长的二进制位数组,初始值全是 0
2.准备多个哈希函数
当一个元素加入布隆过滤器时,比如 "user:1001":用多个哈希函数分别计算位置,假设算出位置是 3、8、15,就把位数组中这些位置设为 1。
查询时也一样,对 "user:1001" 再做同样的多个哈希计算,看 3、8、15 这些位置是不是都为 1。
结果分两种:
1.只要有一个位置是 0,就说明这个元素一定没加入过
2.如果所有位置都是 1,说明它可能加入过

为什么会“可能存在但其实不存在”?
因为不同元素经过哈希后,可能会把同一些位置置为 1

优点:

  • 对恶意构造的大量不存在 key 非常有效
  • 能从源头减少无效数据库访问

注意:

  • 布隆过滤器会有误判率
  • 它只能保证“不会把存在的数据误判为不存在”
  • 适合 key 集合相对稳定的场景

方案三:参数校验

在请求入口做基础参数合法性校验,比如:

  • ID 必须大于 0
  • 手机号格式必须正确
  • 参数长度、类型、范围合法

优点:

  • 简单直接
  • 很多非法请求可以在业务最前面拦截掉

方案四:限流与风控

对于异常流量可以增加:

  • 接口限流
  • 用户限流
  • IP 黑名单
  • 验证码
  • 网关拦截

适用场景:

  • 恶意穿透攻击
  • 异常流量明显时

四、缓存击穿的原因与解决方案

1. 原因

缓存击穿通常发生在:

  • 某个热点 key 访问量非常高
  • 这个热点 key 恰好过期
  • 大量并发请求同时访问
  • 系统没有做并发保护

2. 解决方案

方案一:互斥锁 / 分布式锁

当缓存失效时,只允许一个线程去查数据库和重建缓存,其余线程等待或重试。

流程:

  1. 请求发现缓存失效
  2. 尝试获取锁
  3. 获取锁成功的线程查询数据库并回填缓存
  4. 其他线程等待后重新查缓存

优点:

  • 避免大量请求同时打数据库

注意:

  • 锁要设置过期时间,避免死锁
  • 等待和重试策略要合理
  • 分布式场景通常使用 Redis 锁

方案二:热点数据永不过期

对于极其热点的数据,不设置物理过期时间,而是由业务主动更新缓存。

优点:

  • 不会因为缓存过期导致热点 key 被打穿

缺点:

  • 需要额外机制保证数据更新

方案三:逻辑过期 + 异步刷新

缓存中不只存数据,还存一个“逻辑过期时间”。

请求到来时:

  • 如果未过期,直接返回
  • 如果已过期,先返回旧数据,同时异步触发缓存刷新

优点:

  • 用户请求不会直接穿透数据库
  • 对热点 key 很有效

缺点:

  • 短时间内可能读到旧数据
  • 适合允许最终一致性的业务

方案四:热点数据预热

在活动开始前、系统启动前,提前把热点数据加载到缓存中。

适用场景:

  • 秒杀
  • 大促活动
  • 首页热点推荐
  • 排行榜

五、缓存雪崩的原因与解决方案

1. 原因

缓存雪崩常见原因包括:

  • 大量缓存设置了相同过期时间
  • Redis 宕机
  • 缓存集群异常
  • 网络故障导致缓存层不可用
  • 系统重启导致缓存大量失效

2. 解决方案

方案一:过期时间加随机值

不要让一批缓存同一时刻失效。

例如:

  • 原本 TTL:3600
  • 改为:3600 + random(1~300)

这样可以让不同 key 的过期时间分散开来,避免集中失效。

优点:

  • 简单有效
  • 是防止雪崩最常见的手段之一

方案二:缓存高可用

提升缓存服务本身的可用性,例如:

  • Redis 主从复制
  • Redis Sentinel
  • Redis Cluster
  • 持久化机制
  • 多节点部署
  • 多机房容灾

目标:

  • 避免单点故障导致整个缓存层失效

方案三:限流、降级、熔断

当缓存异常时,必须保护数据库和后端服务。

常见措施:

  • 限流
  • 服务降级
  • 熔断
  • 返回默认值
  • 返回静态页面
  • 非核心接口暂时关闭

示例:

  • 推荐服务降级为默认推荐
  • 商品详情暂时展示基础信息
  • 非核心接口直接提示“稍后重试”

方案四:多级缓存

构建多层缓存体系,例如:

  • 本地缓存
  • Redis 分布式缓存
  • 数据库

这样即使 Redis 出现问题,本地缓存仍能承接一部分热点流量。


方案五:热点预热

提前将高频访问数据写入缓存,避免缓存冷启动带来的瞬时数据库压力。


方案六:核心数据不过期,后台异步更新

对于特别重要的热点数据,可以不依赖 TTL 自动过期,而是通过:

  • 定时任务
  • 消息通知
  • 后台刷新

来主动更新缓存。


##* 六、三者对比总结

对比维度 缓存穿透 缓存击穿 缓存雪崩
数据是否存在 不存在 存在 一般存在
问题对象 不存在的数据 单个热点 key 大量 key / 整个缓存层
并发特征 重复访问无效数据 高并发访问一个热点数据 大量请求同时失去缓存保护
危害 数据库被无效请求拖垮 热点数据库查询暴增 数据库和系统整体压力骤增
常见原因 恶意攻击、非法参数 热点 key 过期 批量过期、Redis 宕机
核心解决方案 空值缓存、布隆过滤器、参数校验 互斥锁、逻辑过期、热点预热 TTL 打散、高可用、限流降级、多级缓存

七、一个形象的理解方式

可以把缓存理解成书店前台的信息牌,数据库理解成后面的仓库。

缓存穿透

有人一直问一本根本不存在的书。

  • 前台信息牌没有
  • 仓库里也没有
  • 店员每次都去仓库查
  • 白跑很多次

缓存击穿

一本特别热门的书,其前台信息牌刚好掉了。

  • 大量顾客同时来问这一本到处都在抢的书
  • 大家发现前台没有信息
  • 店员们一起冲进仓库查同一本书
  • 仓库瞬间被挤爆

缓存雪崩

书店所有书的信息牌在同一时间都掉了,或者前台系统直接坏了。

  • 大量顾客同时来问不同的书
  • 前台都查不到
  • 所有人都去仓库找
  • 整个书店陷入混乱
comments如果有不同意见或者补充,直接留在这里。
contact

在别处继续找到我

如果你想聊技术、设计,或者只是打个招呼。

暂未配置外部链接