Redis 命中率 99%,数据库却 100% CPU,是谁在捣鬼



大家好,我是小米,今年 31 岁,干 Java 也干了不少年头。

前阵子我在公司排查一次线上事故,事情特别"离谱":

Redis 正常、代码没改、数据库却 CPU 100%,连接数飙升,服务雪崩式超时。

当时我第一反应是:"是不是缓存挂了?是不是缓存雪崩?"

结果一查 Redis:稳如老狗,命中率还挺高。

那数据库为啥还能被打爆?最后答案四个字:缓存穿透。

这也是 Java 社招面试里,Redis 相关问题出现频率极高的一道题。

先讲个故事:快递站被"查不存在的包裹"查垮了

为了把缓存穿透讲清楚,我先给你讲个生活中的故事。

假设你家楼下有一个快递站:

  • 前台:小哥(缓存 Redis)
  • 仓库:后面的仓储区(数据库 MySQL)
  • :用户发起查询请求

正常流程是这样的:

  • 你问小哥:"有我这个包裹吗?"
  • 小哥一查系统(Redis)
    • 有 → 直接给你
    • 没有 → 去仓库查(数据库)
  • 仓库查到了 → 拿出来给你
  • 同时小哥把信息记下来,下次就不用再跑仓库

但有一天,事情开始不对劲了......突然来了一群人,天天问:

  • "有单号 -1 的包裹吗?"
  • "有单号 999999999999 的包裹吗?"
  • "有单号 0 的包裹吗?"

这些包裹 根本不存在。结果发生了什么?

  • 小哥查不到(缓存没有)
  • 每次都得跑仓库
  • 仓库也查不到
  • 但你没有任何机制告诉小哥:这玩意本来就不存在

于是:所有请求,100% 穿过前台,全部打到仓库, 仓库再大,也扛不住这种"查空气"的请求。这,就是缓存穿透。

什么是缓存穿透(面试标准答案)

缓存穿透是指:查询的数据在缓存和数据库中都不存在,导致每次请求都会绕过缓存,直接访问数据库,从而在高并发下对数据库造成巨大压力,甚至导致数据库崩溃。

特点非常明显:

  • 查的是 不存在的数据
  • 缓存里没有
  • 数据库里也没有
  • 缓存失效策略对它完全没用

缓存穿透为什么这么危险?

我们用一张表来对比一下几种常见缓存问题:

缓存穿透最恶心的一点是:它是"可被人为制造"的攻击。

只要有人不断请求不存在的 ID,你的数据库就会被持续消耗。

解决方案一:接口层增加基础校验(第一道防线)

先说一句非常现实的话:

很多缓存穿透,本来就不应该进系统。

1、常见的非法请求长这样:

  • id <= 0
  • id 不是数字
  • 用户未登录
  • token 非法
  • 参数明显不合理

2、在接口层直接拦截

这是最便宜、最有效的一层防护。

如果你连 id <= 0 都放进 Redis 和数据库里查:

  • 那不是技术问题
  • 是态度问题

3、这一层的特点

但注意: 这一层 永远不够

解决方案二:缓存空值(key-null 策略)

这是面试里必考的一种方案。

1、核心思想一句话

既然这个数据不存在,那我就明确告诉缓存:它不存在。

2、查询流程升级版

  • 查 Redis
    • 有值 → 返回
    • 是 null → 直接返回空
  • Redis 没有 → 查数据库
  • 数据库也没有?
    • 把 key-null 写进 Redis
    • 设置较短过期时间,比如 30 秒

3、示例代码

4、为什么过期时间要短?

因为:

  • 这个数据 可能未来会被创建
  • 如果你缓存 null 一小时
  • 那真实数据出现后,用户一小时都查不到

5、优缺点分析

解决方案三:布隆过滤器(终极方案,面试加分项)

如果你在面试中能把 Bloom Filter 讲清楚,面试官大概率会在心里默默给你加分。

1、先回到刚才的快递故事

如果快递站门口有一块 超大的白板

  • 所有可能存在的包裹单号
  • 都提前在白板上"打过标记"

那么:

  • 你来查一个单号
  • 白板一看:根本没标记
  • 小哥直接告诉你:不用进站,肯定没有

这块白板,就是 布隆过滤器

Bitmap 与 Bloom Filter 的极致空间利用

1、Bitmap 是什么?

Bitmap 本质上是:用 1 bit 表示一个元素是否存在

典型应用:

  • 用户是否签到
  • 某天是否打卡
  • 某个 ID 是否出现过

2、Bitmap 的问题

布隆过滤器(Bloom Filter)原理详解

1、核心思想

用多个 Hash 函数,降低冲突概率。 不是一个 Hash,而是 k 个 Hash 函数

2、工作流程

插入元素时:

  1. 用 k 个 Hash 函数计算
  2. 得到 k 个位置
  3. 把 bitmap 对应位置全部置为 1

查询元素时:

  • 只要有 一个 bit 为 0,一定不存在
  • 如果全部为 1,可能存在

3、关键结论(面试必背)

  • 不存在 → 一定准确
  • 存在 → 有一定误判率

为什么 Bloom Filter 能防缓存穿透?

因为:

  • 所有 可能存在的数据
  • 在系统启动时就已经加入 Bloom Filter
  • 请求来了先过 Bloom Filter

请求流程变成:

  1. 请求进来
  2. 先查 Bloom Filter
  • 不存在 → 直接返回
  • 可能存在 → 再查 Redis / DB

数据库终于可以喘口气了。

Bloom Filter 的优缺点总结

Redis 中使用 Bloom Filter(实践建议)

在实际项目中,一般有两种方式:

  1. 自己实现 Bitmap + Hash
  2. 使用 RedisBloom 插件

示意代码(简化):

在接口最前面加一层:

三种方案如何组合使用?(标准答案)

真正的生产环境,从来不是"选一个"。 而是:

一句话总结:层层过滤,让请求越早死越好。

面试时怎么一句话总结缓存穿透?

你可以这么说:

缓存穿透是指查询缓存和数据库中都不存在的数据,导致请求绕过缓存直接访问数据库。

我通常通过接口参数校验、缓存空值以及使用布隆过滤器三种方式结合解决。

其中布隆过滤器用于从源头拦截不存在的数据,是大规模系统中最常见的方案。

如果你这么说,面试官基本会点头。

总结

缓存穿透这件事,本质上不是 Redis 的问题,而是你有没有认真思考过:哪些请求根本不该进数据库。

数据库很贵,也很脆弱。Redis 再快,也扛不住"查空气"。

END

希望这篇文章,能帮你在面试中稳稳拿下这一题,也能在真实项目里,少踩一次坑。

我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号"软件求生",获取更多技术干货!

相关推荐
扎Zn了老Fe2 小时前
告别ID冲突:分布式唯一 ID 生成方案全解析
后端
天天摸鱼的java工程师2 小时前
后端密码存储优化:BCrypt 与 Argon2 加密方案对比
java·后端
关于不上作者榜就原神启动那件事2 小时前
Spring Data Redis 使用详解
java·redis·spring
鱼鱼块2 小时前
React 组件通信实战:从 props 入门到父子协作闭环
前端·react.js·面试
我是你们的明哥2 小时前
kafka如何实现exactly once
后端
王中阳Go2 小时前
全面解析Go泛型:从1.18到最新版本的演进与实践
后端·面试·go
oak隔壁找我2 小时前
Java ThreadLocal详解:原理、应用与最佳实践
后端
代码扳手2 小时前
“老板,我的接口性能还能再快一倍!” — Go微服务gRPC升级实战
后端·go
woniu_maggie2 小时前
SAP暂估科目自动清账
后端