云计算百科
云计算领域专业知识百科平台

Redis 面试高频题:缓存击穿到底是什么?互斥锁和逻辑过期一次讲透

先说结论:

缓存击穿(Cache Breakdown)指的是——某个热点 key 刚好在过期的那一瞬间,被海量并发请求同时打中,缓存里没数据,请求全部砸向数据库,数据库瞬间被压垮。

说白了,它跟"缓存里一直没数据"不是一回事,而是"本来该有的数据,恰好在那一刻失效了"。

那问题来了——平时风平浪静,为什么偏偏过期那一刻就崩?

举个通俗一点的例子。

假设我们按文章 ID 去查文章内容,正常链路是:

先读 Redis,命中就直接返回;

没命中才回源数据库,再把结果写回缓存。

可要是这个 key 刚刚过期,同一毫秒里涌进来几万个请求,它们一查 Redis 全是空,于是几万个请求同时往数据库冲。

数据库的并发能力远低于 Redis,很容易在瞬时高并发下被打满。

可以看到,请求原本都应该命中 Redis,但由于热点 Key 恰好过期,所有请求瞬间涌向数据库。

有人可能会问:

查询的时候不是已经把数据同步进 Redis 了吗,怎么还会压垮库?

关键在"重建缓存"这一步。

从数据库把数据查出来、再写回 Redis,中间要花时间。

从数据库把数据查出来,再写回 Redis,这个过程不是瞬间完成的。

要是涉及多表 Join、聚合统计等复杂查询,重建缓存往往需要几十毫秒,甚至更长时间。

只要这段时间内并发足够高,数据库仍然可能被大量请求压垮。

这种"过期瞬间被并发打穿"的现象,就叫缓存击穿。

那怎么治?

主流就两条路:

一是加互斥锁(本质就是分布式锁)。

二是逻辑过期。

下面把两种方案拆开讲,顺便说清它们的取舍。

方案一:互斥锁(分布式锁)

一句话:谁先抢到锁,谁去重建缓存,其他人排队等。

流程是这样的。

线程 A 来查缓存,发现是空的,于是加一把互斥锁(也就是分布式锁),然后去查数据库、重建缓存、把数据写回 Redis,最后释放锁。

与此同时线程 B 也来查,缓存还是空的(因为 A 还没建完)。

B 也想拿锁,但锁在 A 手里,于是 B 拿锁失败,只能先睡一会儿,再重新尝试。

等 A 把数据写回 Redis、释放锁之后,B 再查缓存,就能直接拿到数据。

核心点:

同一时刻只有一个线程能拿到锁,其余线程全在等待。

好处是数据强一致,坏处是性能偏低——大家都得排队。

方案二:逻辑过期

这个思路和互斥锁不一样,它根本不给热点 key 设物理过期时间。

那怎么知道数据老了没?

答案是:

存数据的时候,额外塞一个 expire 字段进去,用这个字段判断逻辑上过没过期。

看下它的存储结构:

key 还是文章 ID,value 里 id、title 是正常业务字段,我们多加了一个 expire 字段,专门记录这条数据的逻辑过期时间。

具体流程是这样的。

线程 A 查缓存,发现这条数据"逻辑上"已经过期了,于是去读 expire 字段确认——确实过期。

既然过期了,就得重建缓存。

所以 A 先去抢一把互斥锁(注意,这里同样需要加锁,否则多个线程都会同时开启后台重建任务,数据库依然可能被打爆)。

不过和方案一不同的是:

A 抢到锁之后,会异步开启一个后台线程负责重建缓存(查库 → 写回缓存 → 重置 expire → 释放锁)。

重点来了:

线程 A 自己不等待新线程建完,直接把当前这份(已过期的)旧数据返回给用户。

接着线程 C 来了,也发现数据过期,也想拿锁重建。

但锁还在 A 手里,C 拿锁失败——C 也不等待,直接返回旧数据。

如果 C 在这儿死等锁,那不就和互斥锁一模一样了?

所以逻辑过期的精髓就是:

拿不到锁的线程,直接返回旧数据,绝不阻塞。

等重建线程把新数据写回缓存、释放锁之后,再来线程 D,查到的就是最新的、没过期的数据了。

两种方案怎么选?

一句话:

互斥锁保一致性,逻辑过期保可用性。

方案一致性性能适用场景
互斥锁 强一致(其他线程等待,数据一定最新) 较低(线程互相等待) 对一致性要求高,如跟钱相关的业务
逻辑过期 不保证绝对一致(可能返回旧数据) 较高(不阻塞,直接返回) 更看重用户体验,如互联网高并发读多场景

怎么选?

看业务底线:

如果你的数据必须强一致,比如订单、余额这类跟钱打交道的地方,闭眼选互斥锁。

如果你的业务更在意用户体验,宁可先返回旧数据也不能让用户卡住,那逻辑过期更香。

赞(0)
未经允许不得转载:网硕互联帮助中心 » Redis 面试高频题:缓存击穿到底是什么?互斥锁和逻辑过期一次讲透
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!