Redis 缓存雪崩、穿透、击穿、预热

        在实际工程中,Redis 缓存问题常伴随高并发场景出现。例如,电商大促、活动报名、突发新闻时,由于缓存失效导致大量请求访问数据库,导致雪崩击穿穿透等问题。因此,新系统上线前需预热缓存,以应对高并发,减轻数据库压力。本章主要围绕这几个核心问题,针对产生场景、分析原因、并给出相应的解决方案。

常见问题:

1. 缓存穿透

1.1 什么是缓存穿透

一句话,就是查询一条根本没有的记录。它既不存在于redis中,也不存在数据库中。一般我们的查询顺序是先查redis、再查数据库。每次请求最终都会访问到数据库。造成数据库访问压力。这种现象叫缓存穿透

问题描述
        key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源(数据库),从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

1.2 产生原因

  • 查询记录在redis和数据库均不存在

  • 恶意攻击

缓存穿透发生的条件:

  • 应用服务器压力变大
  • redis 命中率降低
  • 一直查询数据库,使得数据库压力太大而压垮

其实 redis 在这个过程中一直平稳运行,崩溃的是我们的数据库(如 MySQL)。

缓存穿透发生的原因:黑客或者其他非正常用户频繁进行很多非正常的 url 访问,使得 redis 查询不到数据库。

1.3 解决方案

方案一:使用布隆过滤器

使用布隆过滤器解决缓存穿透问题具体流程如下:

流程说明:

  • Web端发起请求到App服务应用

  • 经过布隆过滤器判断key是否存在

  • 若不存在,直接返回null

  • 若key存在或者被误判(下文详述),查询redis

  • 若redis中有结果,直接返回

  • 未查到,则查询数据库返回结果

  • 数据库查到结果,返回结果。并将对应key回写redis

  • 未查到,则返回null

方案二:缓存空对象或者默认值

针对要查询的数据,需求层面沟通,可以在Redis里存一个缺省值(比如,零、负数、defaultNull等)。

流程描述:

  • 先去redis查键user:xxxxxx没有,再去mysql查没有获得 ,这就发生了一次穿透现象;

  • 第一次来查询user:xxxxxx,redis和mysql都没有,返回null给调用者;

  • 将 user:xxxxxx,defaultNull 回写redis;

  • 第二次查user:xxxxxx,此时redis就有值了。

可以直接从Redis中读取default缺省值返回给业务应用程序,避免了把大量请求发送给mysql处理,打爆mysql。

但是,此方法只能解决key相同的情况,但是无法应对黑客恶意攻击。

解决方案:

  • 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
  • 设置可访问的名单(白名单):使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。
  • 采用布隆过滤器:布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量 (位图) 和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
  • 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

2. 缓存击穿

2.1 什么是缓存击穿

一句话,就是热点key突然失效了,大量请求暴击数据库。关键词:大量请求、同一个热点key、正好失效。

问题描述
        key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。

2.2 产生原因(发生场景)

  • 某个key以到过期时间,但是还是被访问到

  • key被删除,但是被访问到

  • 如,某电商网站的今日特卖

缓存击穿的现象:
数据库访问压力瞬时增加,数据库崩溃
redis 里面没有出现大量 key 过期
redis 正常运行
缓存击穿发生的原因:redis 某个 key 过期了,大量访问使用这个 key(热门 key)。

2.3 解决方案

方案一:差异失效时间

差异失效时间,对于访问频繁的热点key,不设置过期时间。和上边类似,这里不再赘述。

方案二:采用互斥锁,双检加锁策略更新

 双检锁通常用于解决懒加载中的并发问题,即只有当数据在缓存中不存在时,才进行数据库查询,并且确保只有一个线程进行数据库查询。

基本流程如下:

  1. 当大量请求进来,多个线程同时去查询数据库获取同一条数据;

  2. 在第一个查询数据的请求上使用一个互斥锁来锁定;

  3. 其他的线程走到这一步拿不到锁排队等着;

  4. 第一个线程查询到了数据,然后做缓存;

  5. 后面的线程进来发现已经有缓存了,就直接走缓存。

解决方案
        key 可能会在某些时间点被超高并发地访问,是一种非常 “热点” 的数据。

预先设置热门数据:在 redis 高峰访问之前,把一些热门数据提前存入到 redis 里面,加大这些热门数据 key 的时长。
实时调整:现场监控哪些数据热门,实时调整 key 的过期时长。
使用锁:
就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db。
先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key。
当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key;
当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试整个 get 缓存的方法。

3. 缓存雪崩

3.1 什么是缓存雪崩

指在一个高并发的系统中,由于大量的缓存数据在同一时间段内过期或失效,导致大量请求无法从缓存中获取数据,因此大量并发请求可能导致数据库服务器过载,甚至宕机,从而引发整个系统崩溃。

问题描述
        key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端数据库加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端数据库压垮。

        缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存,前者则是某一个 key 正常访问。

3.2 产生原因

  • 硬件故障。 比如:机房着火、停电等导致redis挂机
  • 软件设计。redis中有大量key同时过期或者大面积失效。导致缓存中查不到对应key。此时访问压力给到数据库。如图示:

实际应用场景中,如:

  • 金融系统股票价格、汇率等数据实时变动;

  • 热点新闻缓存过期时,大量用户访问新闻详情页的请求;

  • 电商平台在大促期间,如“618”、“双十一”等。

  如果这些数据在缓存中的过期时间设置得过于集中,或者缓存服务器突然宕机,那么在数据过期或缓存失效的瞬间,大量请求会直接访问数据库,导致数据库负载剧增,可能引发雪崩

3.3 解决方案

针对雪崩问题的解决方案,一般可以从以下几个角度考虑:

方案一:设置redis的某些key永不过期

简单地使用 SET 命令来添加这个key,不需要调用任何设置过期时间的命令:

SET product:500100 "{\"name\":\"ProductName\", \"price\":99.99, \"status\":\"上架中\"}"

在java中,可以使用Jedis或者RedisTemplate客户端

redisTemplate.opsForValue().set(productCode, productData);

  值得注意的是,可能因为Redis的内存限制或其他原因(如使用了Redis的LRU淘汰策略)而被删除。因此,在设计系统时,除了考虑key的过期时间外,还需要考虑Redis的内存管理和淘汰策略。

方案二:搭建高可用redis集群+持久化

如图,一般线上环境都会有redis集群保证高可用。

图片

对应集群模式:

  • 主从模式(Master-Slave)

  • 哨兵模式 (Sentinel)

  • 分片模式 (Cluster)

关于三种集群模式的优缺点对比,配置这里不再赘述。图示只说明redis集群保障高可用的能力。

方案三:服务降级、熔断、限流

在Spring Cloud中使用Hystrix和Sentinel进行熔断降级和限流,这里给出简单的程序示例。

  • 服务熔断降级 - Hystrix

配置pom.xml

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>  
</dependency>

在启动类上添加@EnableHystrix注解来启用Hystrix。

@SpringBootApplication
@EnableHystrix
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}

在服务调用方法上添加@HystrixCommand注解,并配置相应的降级逻辑。

@Service
public class MyService {@HystrixCommand(fallbackMethod = "fallbackMyMethod")public String busiMethod() {// 调用远程服务}public String fallbackMyMethod() {// 降级逻辑return "Fallback response";}
}

对于Hystrix的YML配置,你可以在application.ymlapplication.properties中设置一些全局参数,例如超时时间、请求缓存等。

hystrix:command:default:execution:isolation:thread:timeoutInMilliseconds: 5000 # 设置命令执行的超时时间fallback:enabled: true # 启用降级逻辑
  • 限流 - Sentinel

pom.xml中添加了Sentinel的依赖。

<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

在启动类上添加@EnableDiscoveryClient@EnableSentinel注解。

@SpringBootApplication
@EnableDiscoveryClient
@EnableSentinel
public class MyApplication {public static void main(String[] args) {SpringApplication.run(MyApplication.class, args);}
}

Sentinel的限流规则通常不是在YML文件中配置的,而是通过Sentinel Dashboard进行动态配置。这里设置一些基础配置,例如数据源等。

application.yml中,你可以配置Sentinel的数据源类型以及Dashboard的地址。

spring:cloud:sentinel:transport:dashboard: 127.0.0.1:8080 # Sentinel Dashboard的地址datasource:flow:default:type: filefile:file-name: classpath:flow-rules.json # 规则文件路径rule-type: flow

需要创建一个规则文件,例如flow-rules.json,并在其中定义限流规则。

[{"resource": "some-resource","count": 10,"grade": 1,"limitApp": "default","strategy": 0}
]

  这里我们针对some-resource的QPS限流规则,允许每秒最多10个请求。Sentinel的主要优势在于其动态规则管理能力,因此通常建议通过Sentinel Dashboard进行实时配置和监控。

当然,对于限流,也可以在网关层面使用Spring Cloud Gateway实现。如下图:

图片

  Spring Cloud Gateway是分布式系统中流量的入口。它需要对大量的请求进行管理和控制,以防止服务调用失败(如超时、异常)导致的请求堆积在网关上。通过在网关层面进行限流,可以快速失败并返回给客户端,从而保护后端服务的稳定性。

方案四:设计多级缓存架构

多级缓存主要从:redis缓存本地缓存Nginx缓存考虑

构建多级缓存架构:nginx 缓存 + redis 缓存 + 其他缓存(ehcache 等)。
使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,该方法不适用高并发情况。
设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。
将缓存失效时间分散开:比如可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

4. 缓存预热

缓存预热是一种缓存优化技术,其核心思想是在系统上线或服务重启之前,提前将相关的缓存数据加载到缓存系统中。这样做的目的是为了避免在实际请求到达时进行缓存项的加载,从而减少了响应时间,提升了系统的性能。

以下是一些常见的缓存预热实现方案:

方案一:批量预加载数据

在系统启动或服务重启时,可以批量查询数据库并将结果存入缓存中。这种方式适用于数据量相对固定且比较小的情况。比如:常用的字典配置信息。

public void preloadCache() {List<String> keys = ... // 获取需要预热的缓存键列表for (String key : keys) {Object value = dao.get(key); // 从数据库中获取数据if (value != null) {redis.set(key, serialize(value), cacheExpiration); // 将数据存入缓存}}
}

方案二:异步预热

 如果预热的数据量很大,或者预热过程比较耗时,可以考虑使用异步任务来执行预热操作。这样可以避免阻塞系统启动,并且可以利用系统的空闲时间来预热缓存。

@PostConstruct
public void startPreloadCacheAsync() {CompletableFuture.runAsync(() -> preloadCache());
}

方案三:定时任务预热

对于某些周期性变化的数据,可以用定时任务来定期预热缓存。例如,对于每天更新的数据,可以在每天的某个固定时间执行预热操作。

@Scheduled(fixedRate = 24 * 60 * 60 * 1000) // 每天执行一次
public void schedulePreloadCache() {preloadCache();
}

方案四、使用SpringBoot InitializingBean接口实现

下面是一个使用InitializingBean接口实现缓存预热的示例:

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;@Component
public class CachePreloader implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {// 获取需要预热的缓存键列表List<String> keysToPreload = ...; for (String key : keysToPreload) {// 从数据源获取数据Object value = dbService.get(key); if (value != null) {// 将数据放入缓存cacheService.put(key, value);}}}
}

总结:

本文主要介绍了redis缓存使用中常见的问题:缓存雪崩、缓存击穿、缓存穿透、缓存预热等。详情归纳如下:

缓存问题产生原因解决方案
缓存雪崩大量缓存失效,导致数据库过载1. 分散缓存失效时间
2. 多级缓存
3. 缓存高可用
4. 服务降级限流
缓存穿透查询不存在的数据,导致数据库过载1. 布隆过滤器
2. 空值缓存
缓存击穿热点数据失效,导致数据库过载1. 热点数据永不过期
2. 使用互斥锁

关于热点数据:

Redis中的热点数据是指在一段时间内访问频次极高、热度很高的数据。这些数据在短时间内被大量请求访问,成为整个Redis缓存系统中被频繁查询或更新的对象。热点数据的特点是访问集中在少数几个Key上,可能导致以下几个问题:

  1. 缓存命中率下降:热点数据如果在缓存中失效,短时间内大量请求涌向数据库以获取该数据,缓存命中率急剧下降,增加了数据库的压力。

  2. 缓存雪崩风险:如果热点数据集中设置了相同的过期时间,过期后同时失效,可能会引发缓存雪崩效应,即大量请求瞬间落库,可能导致数据库不堪重负。

  3. 缓存穿透:在极端情况下,热点数据频繁访问的同时,如果该数据恰巧不在缓存中或者已经被移除,每一条请求都会直接穿透缓存到达数据库,加重数据库的负载。

为了避免和优化热点数据带来的问题,可以采取以下策略:

  • 延长热点数据的缓存有效期:适当延长热点数据在缓存中的存活时间,以减少数据库查询的次数。
  • 使用互斥锁:在更新热点数据时使用分布式锁,确保数据更新时的一致性和完整性,避免频繁更新导致的缓存失效问题。
  • 缓存预热:预测和主动加载热点数据到缓存中,确保在高峰期到来之前数据已经存在于缓存中。
  • 缓存淘汰策略优化:选用合适的淘汰策略,例如Redis的LFU(Least Frequently Used)策略可以优先保留访问频次最高的数据,即热点数据。
  • 数据分片与扩容:如果热点数据集中度非常高,可以考虑将热点数据单独存储或分片存储,并根据访问情况进行动态扩容。

总之,妥善管理和优化Redis中的热点数据对于维持系统性能和稳定性至关重要。

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

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

相关文章

文献阅读:将条形码神经解剖学与空间转录分析相结合,可以识别投射神经元相关基因

文献介绍 「文献题目」 Integrating barcoded neuroanatomy with spatial transcriptional profiling enables identification of gene correlates of projections 「研究团队」 Anthony M. Zador&#xff08;美国冷泉港实验室&#xff09; 「发表时间」 2021-05-10 「发表期…

# #一眼就解密

BUUCTF #一眼就解密 1 下面的字符串解密后便能获得flag&#xff1a;ZmxhZ3tUSEVfRkxBR19PRl9USElTX1NUUklOR30 注意&#xff1a;得到的 flag 请包上 flag{} 提交 flag{THE_FLAG_OF_THIS_STRING}

MPLS-基础、LSR、LSP、标签、体系结构

MPLS技术 MPLS基础 MPLS&#xff1a;转发数据时&#xff0c;只在网络边缘分析IP报文头&#xff0c;不在每一跳都分析&#xff0c;节约了转发时间。 MPLS&#xff1a;Multiprotocol Label Switching&#xff0c;多协议标签交换骨干网技术。主要应用&#xff1a;VPN、流量工程…

Golang学习系列1-pprof性能调优

1. pprof 简述 一位亦师亦友的话让我记忆犹新&#xff0c;他说“学习一个新事务&#xff0c;应该从三个方面入手what,why,how;且三者的重要程度应该是递减”。所以在本文的第一部分先叙述下pprof的what & why。 1.1 What&#xff1f; pprof是golang自身提供的一种性能分…

【C++入门】初识C++

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

稀疏矩阵的三元组表表示法及其转置

1. 什么是稀疏矩阵 稀疏矩阵是指矩阵中大多数元素为零的矩阵。 从直观上讲&#xff0c;当元素个数低于总元素的30%时&#xff0c;这样的矩阵被称为稀疏矩阵。 由于该种矩阵的特点&#xff0c;我们在存储这种矩阵时&#xff0c;如果直接采用二维数组&#xff0c;就会十分浪费…

开启 Keep-Alive 可能会导致http 请求偶发失败

大家好&#xff0c;我是蓝胖子&#xff0c;说起提高http的传输效率&#xff0c;很多人会开启http的Keep-Alive选项&#xff0c;这会http请求能够复用tcp连接&#xff0c;节省了握手的开销。但开启Keep-Alive真的没有问题吗&#xff1f;我们来细细分析下。 最大空闲时间造成请求…

C++Date类的实现

目录 前言&#xff1a; 1.显示日期 2.构造函数与获取某年某月的日期的函数 3.日期比较 4.日期加减天数 5.日期减日期 6.前置后置与-- 7.完整代码 8.测试 总结&#xff1a; 感谢支持&#xff01; 前言&#xff1a; 结合了前面的内容的学习&#xff0c;本篇来对之前的…

基于php医院预约挂号系统

摘 要 随着信息时代的来临&#xff0c;过去的管理方式缺点逐渐暴露&#xff0c;对过去的医院预约挂号管理方式的缺点进行分析&#xff0c;采取计算机方式构建医院预约挂号系统。本文通过阅读相关文献&#xff0c;研究国内外相关技术&#xff0c;开发并设计一款医院预约挂号系统…

语义分割交互式智能标注工具 | 澳鹏数据标注平台

随着人工智能应用的大规模落地&#xff0c;数据标注市场在高速增长的同时&#xff0c;也面临着标注成本的挑战。据IDC报告显示&#xff1a;数据标注在AI应用开发过程中所耗费的时间占到了25%&#xff0c;部分医学类应用一条数据的标注成本甚至高达20元。数据精度的高要求、强人…

【Flink技术原理构造及特性】

1、Flink简介 Flink是一个批处理和流处理结合的统一计算框架&#xff0c;其核心是一个提供了数据分发以及并行化计算的流数据处理引擎。它的最大亮点是流处理&#xff0c;是业界最顶级的开源流处理引擎。 Flink最适合的应用场景是低时延的数据处理&#xff08;Data Processin…

爬虫实战三、PyCharm搭建Scrapy开发调试环境

#一、环境准备 Python开发环境以及Scrapy框架安装&#xff0c;参考&#xff1a;爬虫实战一、Scrapy开发环境&#xff08;Win10Anaconda&#xff09;搭建 PyCharm安装和破解&#xff0c;参考&#xff1a;爬虫实战二、2019年PyCharm安装&#xff08;激活到2100年&#xff09; …

0基础如何进入IT行业?

0基础如何进入IT行业&#xff1f; 简介&#xff1a;对于没有任何相关背景知识的人来说&#xff0c;如何才能成功进入IT行业&#xff1f;是否有一些特定的方法或技巧可以帮助他们实现这一目标&#xff1f;我不知道&#xff0c;我的行业算不算是IT&#xff0c;或者最多是半个IT行…

重磅!openGauss6.0创新版本,带着新特性正式发布了!

&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61c;&#x1f61c; 中国DBA联盟(ACD…

从“量子”到分子:探索计算的无限可能 | 综述荐读

在2023年年末&#xff0c;两篇划时代的研究报告在《科学》&#xff08;Science&#xff09;杂志上引发了广泛关注。这两篇论文分别来自两个研究小组&#xff0c;它们共同揭示了单氟化钙分子间相互作用的研究成果&#xff0c;成功地在这些分子间创造出了分子量子比特。这一成就不…

算法练习—day1

title: 算法练习—day1 date: 2024-04-03 21:49:55 tags: 算法 categories:LeetCode typora-root-url: 算法练习—day1 网址&#xff1a;https://red568.github.io 704. 二分查找 题目&#xff1a; 题目分析&#xff1a; 左右指针分别为[left,right]&#xff0c;每次都取中…

【机器学习】K-近邻算法(KNN)介绍、应用及文本分类实现

一、引言 1.1 K-近邻算法&#xff08;KNN&#xff09;的基本概念 K-近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基于实例的学习算法&#xff0c;它利用训练数据集中与待分类样本最相似的K个样本的类别来判断待分类样本所属的类别。KNN算法…

ArcGIS Pro导出布局时去除在线地图水印

目录 一、背景 二、解决方法 一、背景 在ArcGIS Pro中经常会用到软件自带的在线地图&#xff0c;但是在导出布局时&#xff0c;图片右下方会自带地图的水印 二、解决方法 解决方法&#xff1a;添加动态文本--服务图层制作者名单&#xff0c;然后在布局中选定位置添加 在状…

dhcp中继代理

不同过路由器分配ip了&#xff0c;通过一台服务器来代替&#xff0c;路由器充当中继代理功能&#xff0c;如下图 服务器地址&#xff1a;172.10.1.1/24 配置流程&#xff1a; 1.使能dhcp功能 2.各个接口网关地址&#xff0c;配置dhcp中继功能 dhcp select relay &#xff0…

Qt | 发布程序(以 minGW 编译器为例)

1、注意:修改 pro 文件后,最好执行“构建”>“重新构建项目”,否则 pro 文件的更改将不会反应到程序上。 2、发布程序的目的:就是让编译后生成的可执行文件(如 exe 文件),能在其他计算机上运行。 一、编译后生成的各种文件简介 Qt Creator 构建项目后产生的文件及目录…