只要你的项目里用到了 Redis,在简历上写了“引入缓存减轻数据库压力”,那么面试的时候,有三个词你是绝对绕不开的:缓存穿透、缓存击穿、缓存雪崩

别看这三个词听起来一个比一个吓人,又是“穿”又是“崩”的,其实它们背后的原理非常简单。今天咱们不背八股文,用大白话加生活中的例子,把这三个坑以及填坑的办法一次性讲透。


第一个坑:缓存穿透 (Cache Penetration)

是什么?
用一句话解释:要查的数据,Redis 里没有,数据库(MySQL)里也没有。

想象一下,Redis 就像是小区的保安,MySQL 是小区里的住户。
现在有个黑客(或者恶意请求),疯狂地过来找一个叫“王尼玛”的人,并且他的身份证号是 -1。
保安(Redis)查了一下访客名单,发现没这个人,于是对黑客说:“我不知道,你去问物业(MySQL)吧。”
黑客跑到物业(MySQL)一查,发现数据库里压根就没有 ID 为 -1 的数据。
但是,黑客不信邪,一秒钟发出一万次请求找这个不存在的人。结果就是,每一次请求都会完美避开 Redis,直接砸在 MySQL 上。MySQL 哪受得了这委屈,很快就宕机了。这就是“穿透”。

怎么解决?

  1. 缓存空对象(简单粗暴)
    既然数据库里查不到,那我也别让 Redis 啥也不记。当 MySQL 返回空数据时,我在 Redis 里存一个 key = -1, value = null,并且设置一个较短的过期时间(比如 5 分钟)。
    下次黑客再来查 -1,保安(Redis)一看,名单上有记录,写着“查无此人”,直接把请求挡回去,MySQL 就安全了。
    缺点:如果黑客每次都用不同的假 ID 来查(比如随机生成 UUID),Redis 里会被塞满大量无用的 null 数据,浪费内存。

  2. 布隆过滤器 (Bloom Filter)(高端优雅)
    这玩意儿听起来很高大上,其实它就像是小区门口的一道“超级门禁”。
    它的特点是:如果它说一个数据不存在,那这个数据肯定不存在;如果它说存在,那可能存在(有极小的误判率)
    我们在请求到达 Redis 之前,先让布隆过滤器看一眼。黑客拿一个假 ID 过来,布隆过滤器一看:“系统里绝对没这号人”,直接在门口就把请求丢弃了,根本不给黑客见保安(Redis)和物业(MySQL)的机会。
    优点:极其省内存,防御效果非常好。


第二个坑:缓存击穿 (Cache Breakdown)

是什么?
别和“穿透”搞混了。击穿的意思是:数据库里有这个数据,Redis 里本来也有,但是 Redis 里这个数据刚好过期了。而且,这是一个“热点”数据。

举个例子:微博热搜第一是“某明星宣布结婚”。
几千万人同时在刷新这条微博。这条微博的数据在 Redis 里缓存着,本来岁月静好。突然,这个缓存的过期时间到了(失效了)。
就在失效的这 0.001 秒内,有 10 万个用户的刷新请求过来了。保安(Redis)一看,哎呀数据刚刚过期我给扔了,你们自己去问物业(MySQL)要最新数据吧。
于是,10 万个请求在同一瞬间,像一根利箭一样“击穿”了 Redis,全部扎在 MySQL 上去查询同一条数据。MySQL 瞬间暴毙。

怎么解决?

  1. 互斥锁 (Mutex Lock)
    既然大家都在同一瞬间去查数据库,那我们就排个队呗。
    当 Redis 发现数据过期了,不让这 10 万个请求同时去查 MySQL。而是利用分布式锁(比如 Redis 的 setnx),谁第一个抢到锁,谁去 MySQL 查数据,查完之后把数据重新放回 Redis。
    剩下抢不到锁的 99999 个请求怎么办?让他们稍微等个几十毫秒,然后再查一次 Redis。这时候第一个老哥已经把数据放进 Redis 了,大家直接从缓存拿数据就行了。

  2. 逻辑过期(永不过期)
    我们干脆不在 Redis 里给这个热点数据设置物理上的 expire 过期时间了,而是把过期时间作为一个字段写在数据的内容里,比如 {"data": "明星结婚", "expire_time": "12:00"}。
    用户来查数据时,发现当前时间已经是 12:01,说明“逻辑上”过期了。这时候,我们先把旧数据(明星结婚)返回给用户,同时在后台偷偷开启一个线程,去 MySQL 里查最新数据,查完再更新 Redis。
    这种做法体验最好,哪怕数据过期了,用户也感觉不到卡顿,只是暂时拿到了老数据而已。


第三个坑:缓存雪崩 (Cache Avalanche)

是什么?
如果说“击穿”是单点突破,“雪崩”就是全面大塌方。
它的场景有两个:

  1. 大量的缓存数据,在同一时刻集中过期。(比如你写了一段代码,每天凌晨 12 点把所有商品缓存都设为 2 小时过期,那凌晨 2 点的时候,所有缓存同时失效)。

  2. Redis 服务器直接宕机了。

这时候,原本被 Redis 挡住的海量请求,就像雪崩一样全部涌向 MySQL,直接把数据库压垮。

怎么解决?

  1. 把过期时间打散(针对集中过期)
    千万不要给大量数据设置一模一样的过期时间!我们可以在原本的过期时间上,加上一个随机值(比如 1 到 5 分钟的随机秒数)。
    这样一来,原本要在同一秒过期的商品,现在分散在几分钟内陆陆续续过期,MySQL 完全消化得了,这就是“错峰”。

  2. 搭建高可用集群(针对 Redis 宕机)
    为了防止 Redis 突然挂掉,我们不能只用一台 Redis。通过搭建 Redis Sentinel(哨兵)或者 Redis Cluster(集群),实现主从复制。主机挂了,备用机瞬间顶上,保证缓存服务一直活着。

  3. 限流降级(兜底保命)
    如果真的发生雪崩了,MySQL 马上就要撑不住了怎么办?
    赶紧启动限流机制(比如 Sentinel、Hystrix)。把一些非核心的业务(比如用户的点赞、评论功能)暂时关闭,直接返回“系统繁忙”,保证核心业务(比如下单、支付)还能勉强运转。牺牲局部,保全大局。


简单总结一下

为了方便大家记忆,可以这么在心里默念:

  • 缓存穿透:数据到处都没有。黑客搞鬼,全砸库上。-> 对策:缓存空值、布隆过滤器。

  • 缓存击穿:数据单点过期。爆款热搜失效,单挑数据库。-> 对策:互斥锁、逻辑过期。

  • 缓存雪崩:数据大面积过期或 Redis 挂了。集体失效,数据库直接被埋。-> 对策:过期时间加随机数、Redis 高可用集群、限流降级。

把这三个场景和对应的生活例子记在脑子里,无论是在实际项目开发中,还是和面试官对线,你都能轻松应对,游刃有余。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐