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

news2025/2/13 11:29:40

本文是对 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.coloradmin.cn/o/2297328.html

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

相关文章

DeepSeek-R1复现方案梳理

open-r1 项目地址&#xff1a;https://github.com/huggingface/open-r1 由huggingface组建&#xff0c;目前刚上线2周&#xff0c;发布了最新进展open-r1/update-1&#xff0c;在MATH-500任务上接近deepseek的指标&#xff0c;可以在open-r1/open-r1-eval-leaderboard查看指标的…

用php tp6对接钉钉审批流的 table 表格 明细控件 旧版sdk

核心代码 foreach ($flows[product_list] as $k>$gift) {$items_list[] [[name > 商品名称, value > $gift[product_name] ?? ],[name > 规格, value > $gift[product_name] ?? ],[name > 数量, value > $gift[quantity] ?? ],[name > 单位, v…

大模型RLHF:PPO原理与源码解读

大模型RLHF&#xff1a;PPO原理与源码解读 原文链接&#xff1a;图解大模型RLHF系列之&#xff1a;人人都能看懂的PPO原理与源码解读 本文直接从一个RLHF开源项目源码入手&#xff08;deepspeed-chat&#xff09;&#xff0c;根据源码的实现细节&#xff0c;给出尽可能丰富的训…

SQLite 数据库:优点、语法与快速入门指南

文章目录 一、引言二、SQLite 的优点 &#x1f4af;三、SQLite 的基本语法3.1 创建数据库3.2 创建表3.3 插入数据3.4 查询数据3.5 更新数据3.6 删除数据3.7 删除表 四、快速入门指南4.1 安装 SQLite4.2 创建数据库4.3 创建表4.4 插入数据4.5 查询数据4.6 更新数据4.7 删除数据4…

5、大模型的记忆与缓存

文章目录 本节内容介绍记忆Mem0使用 mem0 实现长期记忆 缓存LangChain 中的缓存语义缓存 本节内容介绍 本节主要介绍大模型的缓存思路&#xff0c;通过使用常见的缓存技术&#xff0c;降低大模型的回复速度&#xff0c;下面介绍的是使用redis和mem0&#xff0c;当然redis的语义…

LangChain系列:LangChain基础入门教程

LangChain 是一个开源框架&#xff0c;旨在简化使用大型语言模型&#xff08;LLM&#xff09;创建应用程序的过程。它为链提供了标准接口&#xff0c;与许多其他工具进行了集成&#xff0c;并为常见应用提供了端到端的链。 LangChain 让 AI 开发人员能够基于大型语言模型&#…

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

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

无人机遥感图像拼接及处理实践技术:生态环境监测、农业、林业等领域,结合图像拼接与处理技术,能够帮助我们更高效地进行地表空间要素的动态监测与分析

近年来&#xff0c;无人机技术在遥感领域的应用越来越广泛&#xff0c;尤其是在生态环境监测、农业、林业等领域&#xff0c;无人机遥感图像的处理与分析成为了科研和业务化工作中的重要环节。通过无人机获取的高分辨率影像数据&#xff0c;结合图像拼接与处理技术&#xff0c;…

基于Springmvc+MyBatis+Spring+Bootstrap+EasyUI+Mysql的个人博客系统

基于SpringmvcMyBatisSpringBootstrapEasyUIMysql的个人博客系统 1.项目介绍 使用Maven3Spring4SpringmvcMybatis3架构&#xff1b;数据库使用Mysql&#xff0c;数据库连接池使用阿里巴巴的Druid&#xff1b;使用Bootstrap3 UI框架实现博客的分页显示&#xff0c;博客分类&am…

Vision Transformer:打破CNN垄断,全局注意力机制重塑计算机视觉范式

目录 引言 一、ViT模型的起源和历史 二、什么是ViT&#xff1f; 图像处理流程 图像切分 展平与线性映射 位置编码 Transformer编码器 分类头&#xff08;Classification Head&#xff09; 自注意力机制 注意力图 三、Coovally AI模型训练与应用平台 四、ViT与图像…

软考高级《系统架构设计师》知识点(二)

操作系统知识 操作系统概述 操作系统定义&#xff1a;能有效地组织和管理系统中的各种软/硬件资源&#xff0c;合理地组织计算机系统工作流程&#xff0c;控制程序的执行&#xff0c;并且向用户提供一个良好的工作环境和友好的接口。操作系统有三个重要的作用&#xff1a; 管理…

DeepSeek的魔法:如何让复杂概念变得通俗易懂?

日常生活中&#xff0c;常常会被复杂的概念所困扰。怎么样将这些晦涩难懂的概念变得通俗易懂&#xff1f;当然是利用大模型帮我们解答&#xff0c;不过让大模型解答也需要有好的沟通提示词。  我收集整理了 2 套提示词&#xff0c;大家一起学习一下。  一、用推理模型解释概…

地弹噪声【信号完整性】

地弹、振铃、串扰、信号反射 地弹,就是地噪声! 低频时,地噪声主要是因为构成地线的导体有“电阻”,电路系统的电流都要流经地线而产生的电势差波动。 高频时,地噪声主要是因为构成地线的导体有“电感”,电路系统的电流快速变化地经过这个“电感”时,“电感”两端激发…

【大模型】阿里云百炼平台对接DeepSeek-R1大模型使用详解

目录 一、前言 二、DeepSeek简介 2.1 DeepSeek 是什么 2.2 DeepSeek R1特点 2.2.1 DeepSeek-R1创新点 2.3 DeepSeek R1应用场景 2.4 与其他大模型对比 三、阿里云百炼大平台介绍 3.1 阿里云百炼大平台是什么 3.2 阿里云百炼平台主要功能 3.2.1 应用场景 3.3 为什么选…

相似性图相关性重构网络用于无监督跨模态哈希

《Similarity Graph-correlation Reconstruction Network for unsupervised cross-modal hashing》 摘要1. 引言2. 相关工作2.1. 监督跨模态哈希方法2.2. 无监督跨模态哈希方法 3. 方法论3.1 问题定义3.2 特征提取3.3 模态内关系图构建3.4. 局部关系图重置3.5. 跨模态关系图构建…

问卷数据分析|SPSS实操之单因素方差分析

适用条件&#xff1a; 检验分类变量和定量变量之间的差异 分类变量数量要大于等于三 具体操作&#xff1a; 1.选择分析--比较平均值--单因素ANOVA检验 2. 下方填分类变量&#xff0c;上方为各个量表数据Z1-Y2 3. 点击选项&#xff0c;选择描述和方差齐性检验 4.此处为结果数…

更加通用的Hexo多端部署原理及实现,适用于各种系统之间

本文推荐在作者的个人博客网站阅读&#xff1a;shenying.online 一、故事背景 故事发生在大学上学期间&#xff08;而不是寒假&#xff09;。上学期间&#xff0c;宿舍条件极其恶劣&#xff0c;半夜断电、空间狭小。我们大学垃圾条件使用游戏本的种种弊端被无限放大&#xff1…

5g基站测试要求和关键点

5G基站的测试要求涉及多个方面&#xff0c;以确保其性能、覆盖能力、稳定性和合规性。以下是5G基站测试的主要要求和关键点&#xff1a; 一、基础性能测试 射频&#xff08;RF&#xff09;性能测试 发射机性能&#xff1a;验证基站的发射功率、频率误差、调制质量&#xff08;E…

算法——搜索算法:原理、类型与实战应用

搜索算法&#xff1a;开启高效信息检索的钥匙 在信息爆炸的时代&#xff0c;搜索算法无疑是计算机科学领域中熠熠生辉的存在&#xff0c;它就像一把神奇的钥匙&#xff0c;为我们打开了高效信息检索的大门。无论是在日常生活中&#xff0c;还是在专业的工作场景里&#xff0c;…

【嵌入式Linux应用开发基础】open函数与close函数

目录 一、open函数 1.1. 函数原型 1.2 参数说明 1.3 返回值 1.4. 示例代码 二、close函数 2.1. 函数原型 2.2. 示例代码 三、关键注意事项 3.1. 资源管理与泄漏防范 3.2. 错误处理的严谨性 3.3. 标志&#xff08;flags&#xff09;与权限&#xff08;mode&#xff…