AI智能摘要
文章用奶茶比喻讲解Redis缓存三大失效场景:缓存击穿指热点数据过期瞬间高并发打库;防御可加互斥锁或设长过期并后台刷新。缓存穿透是反复查询根本不存在的数据,方案是缓存空值或布隆过滤器。缓存雪崩为大量key同时失效,预防方法为随机过期和多级缓存。
— 此摘要由AI分析文章内容生成,仅供参考。

认识 Redis 缓存:为什么会失效?

在高性能的应用中,我们经常使用 Redis 作为缓存来减轻数据库的压力。当我们查询数据时,优先从 Redis 中获取,如果 Redis 中没有,再去查询数据库,然后将数据放入 Redis,这个过程被称为 缓存命中

用大白话聊聊 Redis 缓存“失灵”:击穿、穿透、雪崩,一文讲透!

但是,缓存并非万无一失。当缓存中的数据失效时,就可能引发一系列问题。我们通常将这些问题归纳为三种常见的“失效”场景:缓存击穿缓存穿透缓存雪崩

别担心,这些名字听起来有点吓人,但理解起来其实很简单。我们可以用一个生动的比喻来解释它们。


缓存击穿:高并发的“致命一击”

概念:

想象一下,你家楼下有一家超火的奶茶店。他们把最受欢迎的“招牌奶茶”提前做好了几杯,放在柜台里,顾客一来就能直接拿走(这就像缓存)。

但是,每杯奶茶都有保质期(缓存过期时间)。当最后一杯招牌奶茶刚好卖完,而此刻大量顾客又同时涌进来,都想买这杯奶茶。店员不得不临时去后厨(数据库)现做,但做一杯奶茶需要时间,后面排队的人越来越多,所有人都只能干等。

这就是缓存击穿。它指的是某个热点数据(招牌奶茶)在缓存过期的一瞬间,同时有大量高并发请求涌入,这些请求会绕过缓存,直接访问数据库,导致数据库瞬间压力过大,甚至宕机。

解决方案:

  1. 设置永不过期:
    • 将热点数据设置一个较长的过期时间。
    • 在后台定时刷新缓存,或者在数据更新时主动删除缓存。
    • 这就像,奶茶店把招牌奶茶保质期延长到很长,并且店员会定期检查,快过期了就主动换新的。
  2. 加互斥锁(推荐):
    • 当第一个请求去查询数据库时,先给这个数据加锁
    • 其他所有后续请求来了,发现这个数据正在被处理,就会在原地等待
    • 等第一个请求处理完毕,将数据放入缓存后,其他请求就能直接从缓存中获取,避免了大量请求同时访问数据库。

代码示例:

下面是一个使用 Java 锁来防止缓存击穿的简单代码示例:

public String getData(String key) {
     // 1. 先从缓存获取
     String value = redisClient.get(key);
     if (value != null) {
         return value;
    }
 ​
     // 2. 缓存中没有,加锁
     synchronized (this) {
         // 3. 再次从缓存获取(双重检查)
         value = redisClient.get(key);
         if (value != null) {
             return value;
        }
 ​
         // 4. 缓存中还是没有,去数据库查询
         value = databaseClient.get(key);
 ​
         // 5. 查到数据,放入缓存
         if (value != null) {
             redisClient.set(key, value, 60); // 设置过期时间
        }
         
         return value;
    }
 }

缓存穿透:查不到的“白忙活”

概念:

继续奶茶店的比喻。如果一个顾客,每次来都点一杯根本不存在的“月球奶茶”。店员每次都得去后厨(数据库)翻找,结果自然是“查无此茶”。如果有很多顾客都被恶意引导,都来点“月球奶茶”,店员们就会一直白忙活,不断地去后厨查询,数据库的压力会越来越大。

这就是缓存穿透。它指的是查询一个根本不存在的数据。由于缓存中本身就没有这个数据,所以每次请求都会穿过缓存,直接打到数据库上。如果恶意攻击者利用这个漏洞,不断发起查询不存在数据的请求,就会导致数据库负载过高。

解决方案:

  1. 缓存空值:
    • 当数据库查询结果为时,将这个空结果也缓存起来,并设置一个较短的过期时间。
    • 这样,下次再有对这个“不存在数据”的请求,就能直接从缓存中获取到空值,而不用再去访问数据库。
    • 就像,店员告诉顾客“没有月球奶茶”,并把这个信息记下来,下次再有人问,就直接回答,不用再进后厨了。
  2. 布隆过滤器(Bloom Filter):
    • 布隆过滤器是一个非常高效的数据结构,它能快速判断一个数据是否存在
    • 在数据写入数据库时,同时将数据的摘要信息放入布隆过滤器。
    • 当有请求过来时,先用布隆过滤器判断这个数据是否存在。如果过滤器说“不存在”,那这个数据就肯定不存在,直接返回,连缓存都不用查。如果过滤器说“可能存在”,再去查询缓存和数据库。
    • 这就像,奶茶店门口有一本厚厚的“菜单”,上面记录了所有存在的奶茶。顾客点单时,先查这本菜单,如果查不到,就直接告诉他没有,省去了去后厨的时间。

代码示例:

 public String getData(String key) {
     String value = redisClient.get(key);
 ​
     // 1. 如果缓存中存在,直接返回
     if (value != null) {
         return value;
     }
 ​
     // 2. 缓存中没有,去数据库查询
     value = databaseClient.get(key);
 ​
     // 3. 数据库查询结果不为空,放入缓存
     if (value != null) {
         redisClient.set(key, value, 60);
     } else {
         // 4. 数据库查询结果为空,也放入缓存,并设置较短的过期时间
         redisClient.set(key, "null", 5); 
     }
     return value;
 }

缓存空值的代码实现非常简单:


缓存雪崩:一起“集体阵亡”

概念:

回到奶茶店。如果店里所有的奶茶,不管招牌的还是普通的,保质期都一样,比如都是上午10点过期。到了10点,所有的奶茶都不能卖了。而此时,刚好是午餐高峰期,大量顾客涌入,所有的请求都因为找不到缓存(奶茶),而涌向了后厨(数据库),导致后厨工作量瞬间暴增,瘫痪了。

这就是缓存雪崩。它指的是在某一个时间点,大量的缓存同时失效。由于这些请求无法命中缓存,导致所有请求都涌向数据库,在瞬间对数据库造成极大的冲击。

解决方案:

  1. 设置随机过期时间(推荐):
    • 给不同的缓存数据设置不同的过期时间
    • 比如,给缓存A设置5分钟过期,给缓存B设置5分10秒过期,给缓存C设置4分50秒过期。
    • 这样,即使到了某个时间点,也只有少量缓存会失效,而不是全部失效,从而将数据库压力分散开来。
  2. 多级缓存:
    • 使用多级缓存,比如使用 EhcacheCaffeine 等本地缓存。
    • 当 Redis 缓存失效时,请求先尝试从本地缓存中获取,如果本地缓存中还有,就可以直接返回。
    • 只有本地缓存也失效时,才去访问数据库。

代码示例:

设置随机过期时间非常简单,只需要在设置缓存时稍作修改:

 // 正常设置过期时间
 redisClient.set(key, value, 60);
 ​
 // 设置随机过期时间,在60-120秒之间
 // 假设 rand是一个随机数生成器,rand.nextInt(60) 生成0-59的随机数
 int randomExpiration = 60 + new Random().nextInt(60); 
 redisClient.set(key, value, randomExpiration);

总结

问题名称发生原因解决方案
缓存击穿热点数据失效,高并发请求同时涌入互斥锁永不过期
缓存穿透查询不存在的数据,请求穿透缓存缓存空值布隆过滤器
缓存雪崩大量缓存在同一时间失效随机过期时间多级缓存
嗨!欢迎来到我的小世界。 我是来自安徽理工大学的一名计算机学生,一个在代码和咖啡之间穿梭的数字游民。我的技术旅程始于 Java 的严谨逻辑,在 Python 的优雅中找到了快速实现的乐趣,然后又被 React 和 Vue 的前端魅力深深吸引。我喜欢从零开始,用代码构建一个完整的应用,从后端的服务设计到前端的像素级实现,每一步都充满挑战与创造的快感。 我坚信生活不止眼前的 bug,还有诗和远方。我的镜头记录着校园四季的变幻,也捕捉着城市街头的光影故事。当你在这里看到一些关于摄影的分享,请不要惊讶,那是我在代码之外的另一种表达方式。此外,我还喜欢在周末骑着单车,穿梭于乡间小道,享受风带来的自由。这些爱好让我保持着对世界的好奇心和对生活的热情。 这个博客是我分享技术心得、记录成长轨迹、展示个人爱好的地方。在这里,你可能会看到: Java、Python、React、Vue 等技术深度解析 项目开发中的踩坑记录与解决方案 摄影作品与拍摄技巧分享 户外骑行或徒步的游记随笔
最后更新于 2025-08-11