今天我们来攻克由单个热点Key引发的“击穿”危机,掌握加锁和异步更新的核心技巧。
在上一篇《Redis缓存三大难题之缓存穿透:原理+解决方案,一文吃透!》中,我们深入探讨了“无中生有”的缓存穿透问题,很多同学反馈终于能分清“穿透”和“击穿”了。今天咱们趁热打铁,拆解第二个高频难题——缓存击穿。
这是秒杀、热点商品等高并发场景中最容易翻车的地方。先记住这个核心差异:缓存穿透是库里压根没数据,请求像透视眼一样直接穿过缓存打到库里;而缓存击穿是库里明明有数据,但缓存里的那个“超级热点”正好过期了。

1. 彻底理解:什么是缓存击穿
在业务中,总有一些“明星数据”,比如双11的爆款单品、热搜第一的娱乐新闻。它们就像战场上的核心碉堡,被成千上万个请求同时盯着。

想象一下:这个热点数据的缓存原本跑得好好的,突然过期时间到了,缓存失效。就在这短短的几毫秒内,还没等后端把新缓存补上,海量的请求已经像海啸一样涌了过来。大家发现缓存没了,于是全部转头冲向数据库。
结果显而易见:Redis原本能扛住10万+的并发,现在这股压力全压到了只能扛几千并发的数据库身上。数据库瞬间“瘫痪”,这就是缓存击穿。这里要稍微留意,击穿是个“点”的问题(某个热点Key),如果是一大批Key同时失效,那叫“雪崩”,咱们下篇再细聊。
2. 如何防御?两招给数据库穿上防弹衣
解决击穿的核心逻辑就两条路:要么不让这么多请求同时钻空子,要么干脆不让热点数据在关键时刻过期。
(1) 第一招:互斥锁(排队上车,拒绝拥挤)
这是最经典的方法。既然缓存没了,那我们就给“查库”这个动作加一把锁。
当大批请求发现缓存失效时,它们必须去争夺一把“互斥锁”(比如Redis的SETNX)。只有抢到锁的那个“幸运儿”才能获得查库并更新缓存的资格。至于没抢到锁的其他请求,要么稍微“睡个觉”重试,要么直接拿个临时的默认值(比如“加载中”)先应付一下。
这里有个老司机才知道的细节:用这招一定要给锁设个过期时间。万一那个抢到锁的线程中途宕机了没放锁,其他请求就会全部死等,导致整个系统停摆。这种方案虽然让请求稍微等了一会儿,但它能保证你拿到的数据永远是最新的。
(2) 第二招:逻辑过期(永不过期,后台刷新)
如果你的业务对性能要求极高,连那点加锁的等待时间都不能接受,那“逻辑过期”就是大厂更常用的进阶方案。
核心逻辑很有意思:我们在Redis里存数据时,不设真正的过期时间。那怎么知道数据旧了呢?我们在数据内部加一个字段,比如expire_at: 12:00。当请求发现当前时间已经是12:01时,它会先把旧数据返回给用户(保证不卡顿),同时后台偷偷启动一个异步线程去刷新缓存。
这种方案通过牺牲一点点“数据实时性”,换取了极致的响应速度。它非常适合像热门文章、首页推荐这种数据稍微旧几秒也没关系的场景,从根本上杜绝了因过期导致的瞬间冲击。
3. 深度复盘:我该选哪一个
选哪一方案,主要看你的业务更在乎什么。如果你的数据要求绝对准确(比如秒杀库存),那就用互斥锁,慢一点但准;如果你的业务追求极速响应(比如内容资讯),那就用逻辑过期,旧一点但稳。
另外很多同学会问:击穿和穿透会同时发生吗?答案是不会。因为击穿的前提是“库里有数据”,而穿透的前提是“库里没数据”,两者的药方也完全不同,千万别混用了。
4. 核心总结
今天我们搞定了单个热点Key引发的“击穿”危机,掌握了加锁和异步更新的妙招。
至此,Redis三大难题我们已经攻克了两个:
缓存穿透:无数据可查,入口拦截+布隆过滤器。缓存击穿:单点热点过期,互斥锁+逻辑过期。