SpringCache配置Redis有效解决缓存击穿和缓存雪崩问题

2023-10-26 04:15

本文主要是介绍SpringCache配置Redis有效解决缓存击穿和缓存雪崩问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

初始代码

作者参考的一篇CSDN的配置函数代码,实在不好意思,作者忘记是哪位博主了:

    /*** 设置CacheManager缓存规则* @param factory* @return*/@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();//解决查询缓存转换异常的问题ObjectMapper om = new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,ObjectMapper.DefaultTyping.NON_FINAL);GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(om);// 配置序列化(解决乱码的问题),过期时间600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))//.entryTtl(randomTtl())// 使用随机TTL.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}

上述代码配置了一个RedisCacheManager,这是Spring Cache与Redis集成的核心部分。代码中进行了以下配置(了解即可):

  1. 键和值的序列化

    • 为键使用了StringRedisSerializer,这意味着你的缓存键将存储为字符串。
    • 为值使用了GenericJackson2JsonRedisSerializer,该序列化程序基于Jackson,并能处理复杂的Java对象。
  2. JSON映射配置

    • 通过ObjectMapper配置来确保所有属性都是可见的,并默认序列化所有非final类的对象。这有助于确保多态类型的正确序列化。
  3. 缓存配置

    • 缓存条目的默认过期时间设置为600秒
    • 禁用了null值的缓存,这意味着如果某个方法返回null,它不会被缓存。

总的来说,这个配置为一个基本的Redis缓存机制提供了一个很好的起点。但还需要注意考虑一些策略来处理高并发环境下的缓存击穿和雪崩问题。


缓存击穿与缓存雪崩

缓存击穿和缓存雪崩是缓存系统中的两个常见问题,特别是在高并发环境下。它们可能导致系统性能下降,甚至造成服务中断。下面我将分别解释这两个问题,并提供相应的解决策略:

  1. 缓存击穿

    • 描述:指的是一个已经在缓存中的键突然变得非常热门。当该键的缓存到期时,大量的请求会同时去数据库中查询数据,从而可能导致数据库压力过大。
    • 解决策略:
      • 互斥锁第一个请求负责从数据库中加载数据并放入缓存,其余请求等待第一个请求处理完毕。(本文通过互斥锁解决缓存击穿问题,后文中有详细介绍)
      • 设置不过期:对于某些热门键,可以设置为永不过期,并在应用中手动管理其生命周期。
  2. 缓存雪崩

    • 描述:当多个缓存键同时到期,可能导致大量的请求同时访问数据库,从而导致数据库崩溃。
    • 解决策略:
      • 随机设置过期时间给每个缓存键设置一个随机的过期时间,避免多个键同时过期。(本文暂时利用此方法缓解雪崩问题)
      • 双层缓存:使用两层缓存,一层过期时间稍长,另一层过期时间稍短。当短的过期时,从长的获取数据,并异步更新长缓存中的数据。
      • 预热缓存:在某些键即将过期前,预先从数据库中加载数据到缓存。(好帅)
      • 使用熔断机制:在数据库压力过大时,暂时停止部分或所有请求,等待系统恢复。(好帅,好想完成)

除了上述策略,还有一些其它的策略可以应对这两个问题,例如使用限流算法(如Token Bucket、Leaky Bucket)来限制对数据库的访问频率,或者使用备份数据库来分散读取的压力。


代码实现

基于之前的代码,以下是如何增加处理缓存击穿和缓存雪崩的策略:

缓存击穿的解决策略

当我们讨论缓存击穿时,我们通常是指某一个缓存键值突然变得非常受欢迎。例如,想象一下有一个热门商品的数据被缓存,但是这个缓存即将到期。当缓存真正到期时,大量的并发请求可能会试图获取这个数据,发现缓存中没有数据,然后都会去数据库中查找。这可能会导致数据库压力过大。

这里的互斥锁策略的想法是:当缓存过期并且有多个线程尝试获取数据时,只让一个线程去数据库中查询数据并更新缓存,而其他线程则等待这个线程完成操作。这样,数据库只接受一个查询请求,而不是多个并发请求。

这可以使用Spring的@Cacheablesync属性实现。在@Cacheable注解中,添加sync属性:

@Cacheable(value = "allUsers", sync = true)
@Override
public List<User> selectAll() {return userRepository.findAll();
}

sync = true时,如果缓存值不存在,则只有一个线程会执行方法selectAll()并获取数据放入缓存中,其他的并发线程将会等待,直到第一个线程完成操作并将数据放入缓存。这就减少了对数据库的并发请求。

这个策略的主要好处是减轻了数据库的压力,但缺点是当缓存过期时,访问这个数据的请求可能会有一些延迟,因为它们可能需要等待其他线程完成数据加载。


缓存雪崩的解决策略

给每个缓存键设置一个随机的过期时间来避免许多键同时过期。这可以在RedisCacheConfiguration中设置:

首先,我们为缓存的TTL(time-to-live)定义一个随机范围:

import java.util.concurrent.ThreadLocalRandom;...private Duration randomTtl() {int baseTtl = 600; //基础TTLint randomRange = 120; //随机范围,比如+/- 120秒int randomTtl = baseTtl + ThreadLocalRandom.current().nextInt(-randomRange, randomRange);return Duration.ofSeconds(randomTtl);
}

接着,将RedisCacheConfiguration中的TTL设置为随机TTL:

RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(randomTtl()) // 使用随机TTL.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();

现在,每次创建新的缓存条目时,都会为其分配一个在给定范围内的随机TTL。


缓存击穿中的互斥锁问题

显而易见,随机TTL带来的风险比较小,我们可以暂时忽略它,但是,互斥锁,真的是可以随意使用吗?

互斥锁在解决缓存击穿问题上是很有用的,但是不应该盲目地在每一个键上都使用它。以下是考虑使用互斥锁的情境:

  1. 热门的键:如果一个键非常热门,并且在其缓存条目过期后预期会有大量并发请求,那么为这样的键设置互斥锁是有意义的。

  2. 高代价的数据加载:如果从数据源加载数据(例如从数据库或其他服务)的代价非常高,那么即使键不是特别热门,也可能值得使用互斥锁来避免多次并发加载。

  3. 高并发环境:在高并发环境中,即使某些键不是每时每刻都很热门,它们也可能在某个时间点突然变得很受欢迎。在这种情况下,互斥锁也可以提供帮助。

然而,如你所提到的,使用互斥锁也有一些开销:

  • 性能开销:当多个线程因为锁而被迫等待时,会有一定的性能损失。

  • 资源消耗:锁机制需要额外的资源来管理。

  • 增加复杂性:引入锁机制可能会使代码和系统架构变得更加复杂。

综上所述,确实不建议在所有键上都使用互斥锁。它应当被视为一个工具,只在确实需要时使用而不是一种普遍的策略。在决定使用互斥锁之前,应该根据具体应用的需求和场景进行深入的评估。


作者对待互斥锁的方式

对于作者简单的系统,互斥锁暂时是用不了。但作者本着“学都学了”的原则,考虑在测试中模拟缓存击穿问题,然后验证互斥锁的能力。下面是一个简单的方法来模拟和测试:

  1. 创建一个热门的键:选择一个经常被查询的数据对象,例如一个特定的User对象。

  2. 手动清除这个键的缓存:使用Spring的CacheManager手动清除特定的缓存键值。

  3. 使用压力测试工具:使用Apache JMeter压力测试工具,创建大量并发的请求来查询这个User对象。

  4. 观察没有互斥锁时的表现

    • 检查数据库的负载和查询次数
    • 观察响应时间
  5. 启用互斥锁:在查询这个User对象的方法上加上@Cacheable(sync = true)

  6. 再次执行压力测试:与第3步相同,创建大量并发请求。

  7. 观察有互斥锁时的表现

    • 再次检查数据库的负载和查询次数。理论上,查询次数应该大大减少
    • 观察响应时间。可能会看到稍微增加的响应时间,因为一些请求可能在等待锁。不过也不一定,响应时间也可能会比没有互斥锁的情况要快,特别是在高并发情况下,一旦缓存被成功填充,后续的请求将会得到非常快的响应时间。

这篇关于SpringCache配置Redis有效解决缓存击穿和缓存雪崩问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/285417

相关文章

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

wolfSSL参数设置或配置项解释

1. wolfCrypt Only 解释:wolfCrypt是一个开源的、轻量级的、可移植的加密库,支持多种加密算法和协议。选择“wolfCrypt Only”意味着系统或应用将仅使用wolfCrypt库进行加密操作,而不依赖其他加密库。 2. DTLS Support 解释:DTLS(Datagram Transport Layer Security)是一种基于UDP的安全协议,提供类似于

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

如何解决线上平台抽佣高 线下门店客流少的痛点!

目前,许多传统零售店铺正遭遇客源下降的难题。尽管广告推广能带来一定的客流,但其费用昂贵。鉴于此,众多零售商纷纷选择加入像美团、饿了么和抖音这样的大型在线平台,但这些平台的高佣金率导致了利润的大幅缩水。在这样的市场环境下,商家之间的合作网络逐渐成为一种有效的解决方案,通过资源和客户基础的共享,实现共同的利益增长。 以最近在上海兴起的一个跨行业合作平台为例,该平台融合了环保消费积分系统,在短

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]