什么是缓存穿透?该如何解决?

摘要

什么是缓存穿透?该如何解决? 一、什么是缓存穿透 缓存的正常流程:请求先查缓存,命中就直接返回;缓存没命中,才去查数据库,查到结果写入缓存。 缓存穿透:查询一个【缓存和数据库里都不存在的数据】。 缓存中查不到 → 每次请求都穿透缓存,直接打...

什么是缓存穿透?该如何解决?

一、什么是缓存穿透

缓存的正常流程:请求先查缓存,命中就直接返回;缓存没命中,才去查数据库,查到结果写入缓存。

缓存穿透:查询一个【缓存和数据库里都不存在的数据】。 缓存中查不到 → 每次请求都穿透缓存,直接打到数据库。 如果有人恶意用大量不存在的ID疯狂请求,数据库会持续承受压力,严重时数据库被打垮。

举个例子:商品ID合法范围是 1~10000,攻击者疯狂传 id=-1、id=999999,数据库每次查询都返回空,这个空结果也不会进缓存,于是每一次请求都访问DB。

✅ 区分三个概念(面试高频)

  • 缓存穿透:查不存在的数据,缓存、DB都没有,请求直达DB
  • 缓存击穿:单个热点key过期,大量并发同时来查这个key,一起访问DB
  • 缓存雪崩:大批量key同时失效 / Redis宕机,大量流量一起打DB

二、解决方案

方案1:缓存空值(最简单常用)

思路:数据库查不到数据时,也往Redis里存入一个空标记(比如 null、空对象),并设置较短的过期时间。 下次再来查询,缓存命中这个空值,直接返回,不再访问数据库。

示例:

key: goods:999999
value: null
expire: 60s  // 空值不要设置太久,防止脏数据

✅ 优点:实现简单,开发成本低 ❌ 缺点:

  1. 如果恶意传入海量不同的不存在key,会往Redis灌入大量无效空key,占用内存
  2. 数据后续新增之后,会存在短暂不一致(空缓存没过期前查不到新数据)

适用:普通业务场景,流量不大的情况。


方案2:布隆过滤器(BloomFilter,应对恶意攻击首选)

思路:在缓存前面加一层布隆过滤器。 布隆过滤器提前存入所有合法有效的业务key。 请求进来先过布隆过滤器:

  • 过滤器判断不存在 → 直接拒绝请求,不去查缓存、不去查DB
  • 过滤器判断存在 → 再继续走缓存→数据库流程

布隆过滤器特点:有误判(假阳性),没有漏判 也就是:过滤器说不存在,一定不存在;过滤器说存在,有可能不存在。

✅ 优点:内存占用极小,拦截效果很强,适合防恶意穿透 ❌ 缺点:

  1. 存在误判,业务上允许少量正常请求被拦截
  2. 数据新增、删除时维护麻烦;布隆过滤器不容易删除元素
  3. 实现复杂,需要单独维护(Redis布隆插件、Guava布隆)

适用:高并发、有被恶意刷接口风险的场景,比如商品、订单查询接口。


方案3:参数校验(前置拦截,第一道防线)

在接口最外层先校验参数合法性,不合法直接返回,不往下走。 比如:

  • ID必须大于0
  • 手机号格式校验
  • 时间范围、分页参数校验
  • 限制参数长度、格式

✅ 优点:零额外中间件开销,拦截明显非法请求 ❌ 缺点:只能拦截格式错误的,拦不住格式合法但本身不存在的数据(比如合法区间内没有的ID)

👉 最佳实践:所有接口都要加参数校验,作为基础防护


方案4:限流熔断(兜底方案)

针对单个接口设置QPS限流,短时间同一个IP或者同一个请求Key访问频率过高,直接拒绝。 就算前面几层防护失效,也可以保护数据库。

三、生产推荐组合方案

参数校验(第一层) + 布隆过滤器(防大量不存在key攻击) + 缓存空值(兜底) + 限流

小业务、简单接口:参数校验 + 缓存空值足够 对外暴露、容易被攻击的高并发接口:参数校验 + 布隆过滤器

四、一句话总结

缓存穿透就是查不存在的数据,绕开缓存直达数据库;优先参数校验,普通场景缓存空值,防恶意攻击用布隆过滤器。