Redis 的缓存雪崩、缓存穿透和缓存击穿详解,并提供多种解决方案


本文是对 Redis 知识的补充,在了解了如何搭建多种类型的 Redis 集群,并清楚了 Redis 集群搭建的过程的原理和注意事项之后,就要开始了解在使用 Redis 时可能出现的突发问题和对应的解决方案。


引言:虽然 Redis 是单线程的,但它可以通过使用非阻塞 I/O 去高效地处理大量并发连接。具有事件驱动机制,可以基于事件循环机制,达到快速响应各种请求。另外,在 Redis 6.0 引入了多线程 I/O,可以利用多核 CPU 提高网络 I/O 的效率,是市面上高效的 NoSQL 缓存组件。尤其是在分布式系统中,Redis 缓存经常被用来减轻数据库的负载,提高系统的响应速度。

在具有高并发的项目环境或者秒杀环境等,前台会产生超量的请求到缓存或者数据库或者项目突然出现攻击,攻击者对某一个缓存键发起大量请求,或者缓存中不存在的数据直接命中数据库等情况。这些情况必定会导致系统性能下降,甚至完全崩溃。下面就常见的三个问题作解释。

文章目录

    • 一、什么是缓存雪崩?
    • 二、怎么解决缓存雪崩
      • 1、缓存预热(主要)
      • 2、过期时间错峰(主要)
      • 3、加锁控制对某个缓存的读线程数量
      • 4、设置二级缓存
      • 5、设置合理的 Redis 集群
      • 6、降级限流
    • 三、什么是缓存穿透?
    • 四、怎么解决缓存穿透
      • 1、缓存空值(主要)
      • 2、布隆过滤器(主要)
      • 3、增强数据的合法性校验(主要)
      • 4、使用分布式锁
      • 5、限流和熔断
      • 6、设置热点数据永不过期
      • 7、二级缓存
    • 五、什么是缓存击穿?
    • 六、怎么解决缓存击穿
      • 1、设置热点数据过期时间
      • 2、使用布隆过滤器
      • 3、使用互斥锁(分布式锁)
      • 4、使用逻辑过期
      • 5、本地缓存和Redis缓存(二级缓存)
    • 七、总结

一、什么是缓存雪崩?

缓存雪崩是指大量缓存数据在同一时间段失效,导致所有请求都涌向数据库,引发数据库崩溃,或者 Redis 宕机导致缓存系统失效。

这个问题的触发是具有条件性的,这个请况的出现原因在于高并发访问时,Redis 创建缓存时每一秒接收到的数据量大,同时这批数据又设置了相同的过期时间,导致的大量缓存数据在同一时间段失效。

所造成的危害就是数据库瞬间承载大量压力,可能直接导致数据库崩溃,系统性能急剧下降,用户体验变差。

缓存雪崩

二、怎么解决缓存雪崩

那如何避免或者解决这种情况呢?上述情况通常可以分为以下几种,我们要做多种措施,保证不会出现缓存雪崩的情况。

1、缓存预热(主要)

Redis 启动时是没有缓存任何数据的,在后续的时间就可能面临大量热点数据的请求,这时没有相应缓存,会全部命中数据库,造成数据库性能出现问题。这个的解决办法一般是 缓存预热

缓存预热就是系统上线时提前将相关的缓存数据直接加载到缓存系统,而不是等到用户请求的时候才将查询数据缓存,这样用户可直接查询事先被预热的数据。这样部署项目一般都在不常访问的时间段,提前缓存热点数据,在后续的时间就不会面临大量访问数据库的情况。

2、过期时间错峰(主要)

缓存预热并不能根本解决问题,在 新热点数据 出现时还是会出现缓存雪崩,例如某些黑料的出现等,这时就需要探究缓存大批失效的根本能问题。

热点数据同时失效的根本原因在于创建缓存时设置的过期时间是固定的,这种方式就会造成一批生成、一批失效。同一批创建的缓存可以 在固定的过期时间加上一个时间的随机值 ,按照项目要求设置随机值范围,就可以避免同时失效。

3、加锁控制对某个缓存的读线程数量

在缓存失效后,通过加锁或者队列的方式,来控制读数据库和写缓存的线程数量,例如对于某个 Key ,只允许一个线程查询数据和写缓存,其他线程等待。

4、设置二级缓存

设置二级缓存或者双缓存策略,A1为原始缓存,A2为拷贝缓存,A1失效后可以访问A2,A1缓存失效的时间可以设置短期,A2缓存可以设置为长期。其中A2可以是热点缓存,将A1中的热点缓存记录并放入A2,应对突发情况。

5、设置合理的 Redis 集群

使用高可用架构,根据自己系统的业务能力,合理设计Redis集群,例如:

  • 小型系统就是用一主一从的集群架构。
  • 对实时性要求不高并且有一定并发问题的系统就使用一主多从的集群架构。
  • 对于实时性要求高,存在高并发的系统使用多主多从的集群架构。

不同的集群架构会合理使用主从复制功能和哨兵模式来避免单点故障和因某个主节点崩溃而导致的系统问题。

6、降级限流

对于出现缓存雪崩后的补救措施可以使用服务降级和请求限流等机制进行补救。服务降级的最终目的就是保证核心服务可用,即使是有损的。服务降级应当提前确定好哪些服务是可降级的,不同情况下不同的服务的优先级也不同。

具体做法可以拒接服务,延迟服务和随即提供服务,或者说禁用某些功能或者禁用某些功能模块。

三、什么是缓存穿透?

缓存穿透是指客户端查询一个缓存中不存在的键,由于缓存中无数据,无法命中,所以每次查询都会直接访问数据库,并且数据库中也没有数据,这样就导致缓存一直无法生效,从而导致数据库负载增加,产生压力过大的情况。

缓存穿透

用户第一次请求,缓存没数据就会触发写缓存操作,第二次请求就会命中缓存。这种问题造成的危害是数据库负载增加,可能引发性能问题,恶意请求可能频繁访问数据库,导致资源浪费。例如如发起为 id-1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

四、怎么解决缓存穿透

1、缓存空值(主要)

顾名思义,就是收到一个查询后,发现缓存和数据库都没有要查询的数据,这时就将查询的数据以 null 值缓存到 Redis 中。

具体来说,当客户端访问数据时,先请求 Redis,但 Redis 中没有数据,这时会访问数据库,但是数据库中也没有数据。因此这个数据穿透了缓存,直击数据库,并且这种状态下该数据还无法生成缓存。

如果大量的请求同时访问这种不存在的数据,那么这些请求就都会访问到数据库,由于数据库能够承载的并发不如 Redis ,就会出现数据库性能出现问题。

解决方案对于数据库中不存在的数据如果被查询到了,就把这个数据存入到 Redis 缓存中去,下次用户过来访问这个不存在的数据,就能在 Redis 缓存中找到。

这种方式也会产生问题,面临大量的恶意请求,会导致缓存逐渐增加,进而影响服务性能,另外,如果该数据被写入到数据库中,缓存还是null值,会造成数据的短期不一致。

2、布隆过滤器(主要)

布隆过滤器是一种基于哈希算法的数据结构,用于快速判断一个元素是否存在于一个集合中。在请求到达缓存之前,先通过布隆过滤器判断该请求是否合法。如果布隆过滤器判断该请求不存在,则直接返回,避免查询数据库。

布隆过滤器的大致原理:布隆过滤器中存放二进制位,数据库的数据通过哈希算法计算其哈希值并存放到布隆过滤器中,后面判断该数据是否存在的时候,就是查找该数据的哈希值是 0 还是 1 。这是一种概率上的统计,能够保证该数据被判断不存在的时候就一定是不存在,被判断存在的时候不一定存在,存在一定的穿透风险。

优点是内存占用较少,适合处理大规模数据,Redis 缓存中也不会存储多余的键。但是实现相对复杂,并且存在一定的误判率,但可以通过调整布隆过滤器的参数来降低误判率。

3、增强数据的合法性校验(主要)

在请求到达缓存和数据库之前,增加对请求参数的合法性校验。例如,对于用户ID的请求,可以校验ID是否符合预期的格式或范围。如果参数不合法,则直接返回错误,避免不必要的查询。

4、使用分布式锁

在缓存穿透的场景下,当多个请求同时发现缓存中没有数据时,可以使用分布式锁(如 Redis 的 SETNX 命令)来确保只有一个请求去数据库查询数据并更新缓存,其他请求等待锁释放后直接从缓存获取数据。

5、限流和熔断

对于高频请求,可以采用限流策略,限制单位时间内对某个接口的访问次数。当请求量超过阈值时,直接返回错误或排队等待。此外,还可以结合熔断机制,在缓存穿透导致数据库压力过大时,暂时停止对数据库的访问。

6、设置热点数据永不过期

对于一些热点数据(如热门商品信息),可以设置其缓存永不过期,或者通过后台线程定期更新缓存。这样可以避免因缓存过期而导致的缓存穿透问题。

7、二级缓存

也可以采用缓存雪崩中的二级缓存解决办法,A1为原始缓存,A2为拷贝缓存,A1失效后可以访问A2,A1缓存失效的时间可以设置短期,A2缓存可以设置为长期。其中A2可以是热点缓存,将A1中的热点缓存记录并放入A2,应对突发情况。

不同的解决方案适用于不同的场景,可以根据实际需求选择合适的方法。例如,对于小规模应用,缓存空对象或增强数据校验可能足够;而对于大规模高并发系统,布隆过滤器或分布式锁可能是更好的选择。

五、什么是缓存击穿?

缓存击穿,是指一个 Key 在不停地支撑着高并发,高并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的高并发就穿破缓存,直接请求数据库。缓存击穿和缓存雪崩的区别在于缓存击穿是针对某一个 Key 缓存而盲,缓存雪崩则是针对很多 Key。

缓存击穿

六、怎么解决缓存击穿

首先要知道缓存击穿出现的场景,才好使用对应的解决方案,缓存击穿是指一个 Key 在不停地支撑着高并发,高并发集中对这一个点进行访问,当这个 Key 在失效的瞬间,持续的高并发就穿破缓存,直接请求数据库。

对一般的网站而言,很难有一个 Key 能达到缓存击穿的级别,一般是热门网站的秒杀或爆款商品,才有可能发生这种情况。当发生缓存击穿时,在这个 Key 没有被重新加载到缓存之前,或者过期时间超过抢购时段时是一种很好的避免发生缓存击穿的方法,这时这种可以不需要考虑数据一致性的问题的话。

1、设置热点数据过期时间

首先在秒杀环境中,设置 Key 的过期时间超过秒杀的时间是最好的一种办法,或者对于热点数据,可以设置其缓存永不过期,通过后台线程定期更新缓存。这就不会出现缓存击穿的情况,但是可能会出现数据不一致问题。

// 设置热点数据永不过期
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(data));// 后台线程定期更新缓存
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {Object newData = queryDatabase(key);setCache(key, newData);
}, 0, 10, TimeUnit.MINUTES);

2、使用布隆过滤器

布隆过滤器是用于快速判断一个元素是否存在于一个集合中。首先初始化布隆过滤器,将所有可能的Key加入布隆过滤器,在请求到达缓存之前,先通过布隆过滤器判断该请求是否合法。如果布隆过滤器判断该请求不存在,则直接返回,避免查询数据库。在缓存穿透的解决方案中也推荐过这种方法。

// 初始化布隆过滤器
BloomFilter<String> bloomFilter = new BloomFilter<>(100000, 0.01);
bloomFilter.add("hotKey1");
bloomFilter.add("hotKey2");// 查询缓存
if (!bloomFilter.contains(key)) {return null; // Key不存在,直接返回
}
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {return JSONUtil.toBean(json, type);
}

3、使用互斥锁(分布式锁)

在查询缓存时,如果发现缓存失效,尝试获取分布式锁。通过分布式锁(如Redis的SETNX命令)确保只有一个线程去查询数据库并更新缓存,其他线程等待锁释放后直接从缓存获取数据。如果获取锁成功,则查询数据库并更新缓存。如果获取锁失败,则等待锁释放后再次查询缓存。

String lockKey = "lock:" + key;
boolean isLock = tryLock(lockKey);
if (isLock) {// 查询数据库并更新缓存Object data = queryDatabase(key);setCache(key, data);
} else {// 等待锁释放后再次查询缓存Thread.sleep(50);return queryCache(key);
}

4、使用逻辑过期

逻辑过期是指在缓存中存储数据时,将数据和过期时间一起存储。当查询缓存时,先检查数据是否过期,如果未过期,直接返回数据,如果已过期,启动独立线程去更新缓存,同时返回旧数据。这样做的优点在于即使缓存已过期,也可以先返回旧数据。

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}// 查询缓存
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotBlank(json)) {RedisData redisData = JSONUtil.toBean(json, RedisData.class);if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {return redisData.getData();} else {// 启动独立线程更新缓存CACHE_REBUILD_EXECUTOR.submit(() -> {try {Object newData = queryDatabase(key);setCacheWithLogicalExpire(key, newData, expireTime);} catch (Exception e) {throw new RuntimeException(e);}});return redisData.getData(); // 返回旧数据}
}

5、本地缓存和Redis缓存(二级缓存)

采用多级缓存架构,在本地缓存中存储热点数据,先查询本地缓存,如果本地缓存失效,则查询Redis缓存。

// 查询本地缓存
Object data = localCache.get(key);
if (data == null) {// 查询Redis缓存String json = stringRedisTemplate.opsForValue().get(key);if (StrUtil.isNotBlank(json)) {data = JSONUtil.toBean(json, type);localCache.put(key, data); // 更新本地缓存}
}
return data;

不同的解决方案适用于不同的场景,可以根据实际需求选择合适的方法。

  • 互斥锁适用于缓存失效时需要保护数据库的场景。
  • 逻辑过期适用于需要快速返回数据且对数据实时性要求不高的场景。
  • 热点数据永不过期适用于热点数据频繁访问的场景。
  • 布隆过滤器适用于数据量大且需要快速判断Key是否存在的场景。
  • 多级缓存适用于需要进一步减轻数据库压力的场景。

七、总结

看到最后的读者也明白了,缓存雪崩、缓存穿透和缓存击穿出现的场景各有不同,但是解决办法都是存在一定的共同点,相似的解决方案基本都是辅助解决问题,而对于不同场景独特的解决方案才是解决该问题的核心。在各种解决方案中,大家一定要注意数据的实时性和一致性,在保障了这两个前提下才能更好的解决问题,实现高可用的系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/70897.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

路由过滤方法与常用工具

引言 在前面我们已经学习了路由引入&#xff0c;接下来我们就更进一步来学习路由过滤 前一篇文章&#xff1a;重发布&#xff1a;路由引入&#xff08;点击即可&#xff09; 路由过滤 定义&#xff1a;路由器在发布或者接收消息时&#xff0c;可能需要对路由信息进行过滤。 作用…

元宵佳节,我的创作纪念日:技术之路的回顾与展望

今天是元宵节&#xff0c;一个象征着团圆与美好的节日。巧合的是&#xff0c;今天也是我作为技术博客博主的创作纪念日。在这个特别的日子里&#xff0c;我想和大家分享我的创作故事&#xff0c;回顾初心、总结收获、展望未来&#xff0c;同时也希望能为正在技术道路上探索的你…

【STM32】H743的以太网MAC控制器的一个特殊功能

调试743的MAC&#xff0c;翻阅手册的时候&#xff0c;发现了一个有意思的功能 混杂模式 H743的MAC控制器&#xff0c;可以设置为混杂模式&#xff0c;这就意味着它可以做一些网络监控的应用&#xff0c;譬如连接具备端口镜像功能的交换机&#xff0c;然后直接代替PC实现网络数据…

一个AI应用的开发、上线流程解析

目录 1. 模型文件格式 1.1 CheckPoint (ckpt) 文件格式 1.2 .pth 文件格式 1.3 .mindir 文件格式 1.4 .onnx 文件格式 2. 推理&#xff08;Inference&#xff09; 2.1.pth (PyTorch模型格式) 2.2 .mindir (MindSpore模型格式) 2.3.onnx (开放神经网络交换格式) 2.4实…

使用grafana v11 建立k线(蜡烛图)仪表板

先看实现的结果 沪铜主力合约 2025-02-12 的1分钟k线图 功能介绍: 左上角支持切换主力合约,日期,实现动态加载数据. 项目背景: 我想通过前端展示期货指定品种某1天的1分钟k线,类似tqsdk 的web_gui 生成图形化界面— TianQin Python SDK 3.7.8 文档 项目架构: 后端: fastap…

【C++】解锁<list>的正确姿势

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

JUC并发—1.Java集合包底层源码剖析

大纲 1.为什么要对JDK源码剖析 2.ArrayList源码一&#xff1a;基本原理以及优缺点 3.ArrayList源码二&#xff1a;核心方法的原理 4.ArrayList源码三&#xff1a;数组扩容以及元素拷贝 5.LinkedList源码一&#xff1a;优缺点和使用场景 6.LinkedList源码二&#xff1a;双…

修改docker内容器中的某配置文件的命令

先找到配置文件config.php find / -name "config.php" 2>/dev/null 然后用vi编辑器修改配置文件 vi /var/www/config.php 最后就是vi的基本操作&#xff0c;根据具体需求使用&#xff1a; vi 有两种主要模式&#xff1a; 命令模式&#xff1a;进入 vi 后的默认…

一竞技瓦拉几亚S4预选:YB 2-0击败GG

在2月11号进行的PGL瓦拉几亚S4西欧区预选赛上,留在欧洲训练的YB战队以2-0击败GG战队晋级下一轮。双方对阵第二局:对线期YB就打出了优势,中期依靠卡尔带队进攻不断扩大经济优势,最终轻松碾压拿下比赛胜利,以下是对决战报。 YB战队在天辉。阵容是潮汐、卡尔、沙王、隐刺、发条。G…

使用Docker部署MySQL 5.7并配置防火墙

步骤1: 切换到超级用户 首先&#xff0c;打开终端&#xff0c;输入以下命令切换到超级用户(root)&#xff1a; su 然后输入您的root密码。 步骤2: 启动Docker服务 确保Docker服务已经启动。可以使用如下命令启动Docker&#xff08;如果它尚未运行&#xff09;&#xff1a;…

vue elementui select下拉库组件鼠标移出时隐藏下拉框

方案&#xff1a; select 监听 mouseleave事件&#xff0c;当鼠标离开时通过唯一标识ref设置select 下拉框隐藏&#xff0c;并做失焦 <el-select v-model"value" :popper-append-to-body"false" class"select_drop_inner" size"sm…

国产操作系统安装DeepSeek

从年前到现在&#xff0c;DeepSeek这款语言AI模型&#xff0c;一经发布直接在全球爆火&#xff0c;在热搜上更是牢牢占据一席之地。无论是技术大神&#xff0c;还是紧跟潮流的技术小白&#xff0c;都被它强大的自然语言处理能力所吸引。作为国产操作系统的用户&#xff0c;千万…

记使用AScript自动化操作ios苹果手机

公司业务需要自动化操作手机&#xff0c;本来以为很困难&#xff0c;没想到使用AScript工具出乎意料的简单&#xff0c;但是还有很多坑存在&#xff0c;写个博客记录一下。 工具信息&#xff1a; 手机&#xff1a;iphone7 系统版本&#xff1a;ios15 AScript官方文档链接&a…

关于conda换镜像源,pip换源

目录 1. 查看当前下载源2. 添加镜像源2.1清华大学开源软件镜像站2.2上海交通大学开源镜像站2.3中国科学技术大学 3.删除镜像源4.删除所有镜像源&#xff0c;恢复默认5.什么是conda-forge6.pip换源 1. 查看当前下载源 conda config --show channels 如果发现多个 可以只保留1个…

Springboot 中如何使用Sentinel

在 Spring Boot 中使用 Sentinel 非常方便&#xff0c;Spring Cloud Alibaba 提供了 spring-cloud-starter-alibaba-sentinel 组件&#xff0c;可以快速将 Sentinel 集成到你的 Spring Boot 应用中&#xff0c;并利用其强大的流量控制和容错能力。 下面是一个详细的步骤指南 …

ARM Cortex-M3/M4 权威指南 笔记【一】技术综述

一、Cortex-M3/M4 处理器的一般信息 1.1 处理器类型 ARM Cortex-M 为 32 位 RISC&#xff08;精简指令集&#xff09;处理器&#xff0c;其具有&#xff1a; 32位寄存器32位内部数据通路32位总线接口 除了 32 位数据&#xff0c;Cortex-M 处理器&#xff08;以及其他任何 A…

(一)Axure制作移动端登录页面

你知道如何利用Axure制作移动端登录页面吗&#xff1f;Axure除了可以制作Web端页面&#xff0c;移动端也是可以的哦&#xff0c;下面我们就一起来看一下Axure制作移动端登录页面的过程吧。 第一步&#xff1a;从元件中拖入一个矩形框&#xff0c;并设置其尺寸为&#xff1a;37…

变化检测相关论文可读list

一些用得上的&#xff1a; 遥感变化检测常见数据集https://github.com/rsdler/Remote-Sensing-Change-Detection-Dataset/ 代码解读&#xff1a;代码解读 | 极简代码遥感语义分割&#xff0c;结合GDAL从零实现&#xff0c;以U-Net和建筑物提取为例 NeurIPS2024: https://mp.w…

从深入理解 netty——》AI

想了很久&#xff0c;准备写一个系列从深入理解 netty——》AI。 先说下为啥要从netty开始&#xff0c;看看netty的重要性 rocketmq异步消息组件nacos微服务注册中心spring cloud gateway网关redission分布式缓存es全文检索sentinel流量控制&#xff0c;服务保护seata分布式…

Auto-go 环境配置

go环境配置 1.下载 Go 安装包 从 Go 官方网站&#xff08;https://golang.org/dl/&#xff09;下载适合你操作系统的 Go 安装包。不过由于网络原因&#xff0c;可能访问官方网站不太方便可以用我这里的链接Go安装包下载地址点击自动下载 2.下载ide这里使用GoLand 官方网站 …