springboot业务开发--springboot集成redis解决缓存雪崩穿透问题

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 该文介绍了缓存使用中可能出现的三个问题及解决方案:缓存穿透、缓存击穿和缓存雪崩。为防止缓存穿透,可校验请求数据并缓存空值;缓存击穿可采用限流、热点数据预加载或加锁策略;缓存雪崩则需避免同一时间大量缓存失效,可设置随机过期时间。文章还提及了Spring Boot中Redis缓存的配置,包括缓存null值、使用前缀和自定义过期时间,并提供了改造代码以实现缓存到期时间的个性化设置。

一、缓存使用的若干问题

1.1.缓存穿透

正常情况下,我们去查询数据大部分都是存在的。如果请求去查询一条压根儿数据库中根本就不存在的数据,也就是缓存和数据库都查询不到这条数据,但是请求每次都会打到数据库上面去,造成对后端数据库的强大压力。这种查询不存在数据的现象我们称为缓存穿透。(有可能会是某些不法份子的恶意行为,多线程打满去向服务查询不存在的数据)

解决办法

   做好查询请求的数据校验,治标不治本

   缓存空值,之所以会穿透缓存给压力到数据库,就是因为缓存层没有缓存null值。后文会说明在Spring Boot环境下如何配置

   使用redis BloomFilter(这个已经脱离了Spring Boot课程范围,了解即可或自行学习)

1.2.缓存击穿

在平常高并发的系统中,大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。

比如:鹿晗宣布恋情,导致微博瘫痪。就有可能是缓存击穿导致的,大家都去看这一个热点新闻,热点新闻的缓存如果超时失效了,就造成后端服务压力增大,服务器瘫痪。(当然这只是我猜的,举例而已)

解决办法

   可以通过准确的监控热点流量,及时的针对热点服务及缓存组件进行自动化的扩容。

   通过Hystrix或sentinel等服务限流工具,保证系统的可用性,拒绝掉一部分流量的访问。

   第三种方法就是加锁,SpringCache采用sync属性,只有一个线程去维护缓存,其他线程会被阻塞,直到缓存中更新该条目为止。也就是第一次查询只允许一个线程,等数据被缓存之后,才支持并发。

   @Cacheable(value = CACHE_OBJECT,key = "#id",sync=true)    

   public ArticleVO getArticle(Long id) {

1.3.缓存雪崩

同一时刻大量缓存失效,导致请求集中的全部打到数据库。比如:双十一零点搞活动,为了支撑这次活动,事先已经缓存好大量的数据。如果所有的数据全是缓存24小时,那24小时之后这些数据缓存将集中失效,最终结果就是11.12号服务崩溃。

解决办法

   可以通过准确的监控热点流量,及时的针对热点服务及缓存组件进行自动化的扩容。

   不同缓存的失效时间不能一致,同一种缓存的失效时间也尽量随机(最小值-->最大值)

二、redis 缓存配置

在 application.yml指定 spring.cache.type=redis。

   spring:

     cache:

       type: redis

       redis:

         cache-null-values: true   # 缓存null,防止缓存穿透

         use-key-prefix: true  # 是否使用缓存前缀

         key-prefix: boot-launch  # 缓存前缀,缓存按应用分类

         time-to-live:  3600  # 缓存到期时间,默认不主动删除永远不到期

其中值得注意的一点是,Spring Cache默认只支持全局对所有的缓存配置生效时间,不支持对缓存的生效时间分类配置,容易造成缓存雪崩。

三、自定义缓存到期时间

由于redis缓存设置的到期时间是统一的,没有办法根据缓存名称(value属性)分别设置缓存到期的时间,容易造成缓存雪崩。所以我们进行一个简单的改造。在改造之前我们先来看一下RedisCacheManager源码

RedisCacheManager构造函数包含三个参数

   RedisCacheWriter这个在之前的章节我们就配置过

   RedisCacheConfiguration defaultCacheConfiguration 这个是默认的全局配置,针对所有缓存

   Map<String, RedisCacheConfiguration> initialCacheConfigurations这个是针对某一种缓存的个性化配置,泛型String是缓存名称,泛型RedisCacheConfiguration是该缓存的个性化配置

理解了上面的源码,下面的改造代码就不难理解了。

   @Data

   @Configuration

   @ConfigurationProperties(prefix = "caching")  //application.yml配置前缀

   public class RedisConfig {

   

       //11.4章节代码,不是本节内容

       @Bean

       public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {

           RedisTemplate redisTemplate = new RedisTemplate();

           redisTemplate.setConnectionFactory(redisConnectionFactory);

           Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

   

           ObjectMapper objectMapper = new ObjectMapper();

           objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

           objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

   

           jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

   

           //序列化重点在这四行代码

           redisTemplate.setKeySerializer(new StringRedisSerializer());

           redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);

           redisTemplate.setHashKeySerializer(new StringRedisSerializer());

           redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

   

           redisTemplate.afterPropertiesSet();

           return redisTemplate;

       }

   

   

        //从这里开始改造

       //自定义redisCacheManager

       @Bean

       public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {

           RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());

   

           RedisCacheManager redisCacheManager = new RedisCacheManager(redisCacheWriter,

                   this.buildRedisCacheConfigurationWithTTL(redisTemplate,RedisCacheConfiguration.defaultCacheConfig().getTtl().getSeconds()),  //默认的redis缓存配置

                   this.getRedisCacheConfigurationMap(redisTemplate)); //针对每一个cache做个性化缓存配置

   

           return  redisCacheManager;

       }

   

       //配置注入,key是缓存名称,value是缓存有效期

       private Map<String,Long> ttlmap;  //lombok提供getset方法

   

       //根据ttlmap的属性装配结果,个性化RedisCacheConfiguration

       private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap(RedisTemplate redisTemplate) {

           Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();

   

           for(Map.Entry<String, Long> entry : ttlmap.entrySet()){

               String cacheName = entry.getKey();

               Long ttl = entry.getValue();

               redisCacheConfigurationMap.put(cacheName,this.buildRedisCacheConfigurationWithTTL(redisTemplate,ttl));

           }

   

           return redisCacheConfigurationMap;

       }

   

       //根据传参构建缓存配置

       private RedisCacheConfiguration buildRedisCacheConfigurationWithTTL(RedisTemplate redisTemplate,Long ttl){

           return  RedisCacheConfiguration.defaultCacheConfig()

                   .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))

                   .entryTtl(Duration.ofSeconds(ttl));

       }

   

   }

   

四、自定义配置实现缓存失效时间个性化

在 application.yml指定 缓存名称对应的缓存生效时间,单位为秒

   caching:

     ttlmap:

       article: 10

       xxx: 20

       yyy: 50


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
15天前
|
API Java 监控
SpringBoot基于OpenAPI3的接口文档管理快速集成和使用
本文主要简单介绍SpringCloud2023中进行接口文档管理,方便前后端开发和文档维护。文档管理工具基于开源的knife4j封装的openapi3。
47 3
|
17天前
|
缓存 监控 NoSQL
redis 缓存穿透 击穿 雪崩 的原因及解决方法
redis 缓存穿透 击穿 雪崩 的原因及解决方法
|
1天前
|
编解码 Windows
FFmpeg开发笔记(二十九)Windows环境给FFmpeg集成libxvid
XviD是开源MPEG-4视频编码器,与DivX相似但后者非开源。早期MP4常使用XviD或DivX编码,现已被H.264取代。在Windows上集成FFmpeg的XviD编解码库libxvid,需访问<https://labs.xvid.com/source/>下载源码,解压后在MSYS环境中配置、编译和安装。之后重新配置FFmpeg,启用libxvid并编译安装。详细步骤包括configure命令、make和make install。成功后,通过`ffmpeg -version`检查是否启用libxvid。更多音视频开发技术可参考《FFmpeg开发实战:从零基础到短视频上线》。
22 0
FFmpeg开发笔记(二十九)Windows环境给FFmpeg集成libxvid
|
2天前
|
编解码 Linux
FFmpeg开发笔记(二十八)Linux环境给FFmpeg集成libxvid
XviD是开源的MPEG-4视频编解码器,曾与DivX一起用于早期MP4视频编码,但现在已被H.264取代。要集成XviD到Linux上的FFmpeg,首先下载源码,解压后配置并编译安装libxvid。接着,在FFmpeg源码目录中,重新配置FFmpeg以启用libxvid,然后编译并安装。成功后,通过`ffmpeg -version`检查是否启用libxvid。详细步骤包括下载、解压libxvid,使用`configure`和`make`命令安装,以及更新FFmpeg配置并安装。
13 2
FFmpeg开发笔记(二十八)Linux环境给FFmpeg集成libxvid
|
6天前
|
移动开发 前端开发 JavaScript
|
6天前
|
网络协议 JavaScript Java
|
9天前
|
编解码 Linux
FFmpeg开发笔记(二十五)Linux环境给FFmpeg集成libwebp
《FFmpeg开发实战》书中指导如何在Linux环境下为FFmpeg集成libwebp以支持WebP图片编解码。首先,从GitHub下载libwebp源码,解压后通过`libtoolize`,`autogen.sh`,`configure`,`make -j4`和`make install`步骤安装。接着,在FFmpeg源码目录中重新配置并添加`--enable-libwebp`选项,然后进行`make clean`,`make -j4`和`make install`以编译安装FFmpeg。最后,验证FFmpeg版本信息确认libwebp已启用。
17 1
FFmpeg开发笔记(二十五)Linux环境给FFmpeg集成libwebp
|
10天前
|
Java Spring
解决Springboot集成ElasticSearch 报错:A bean with that name has already been defined in null and overriding
解决Springboot集成ElasticSearch 报错:A bean with that name has already been defined in null and overriding
|
13天前
|
搜索推荐 前端开发 JavaScript
SpringBoot静态资源访问控制和封装集成方案
该文档描述了对基于SpringBoot的项目框架进行优化和整合的过程。原先采用前后端分离,后端兼做前端,但随着项目增多,升级维护变得复杂。因此,决定整合后台管理页面与后端代码,统一发布。设计上,框架包含后台管理资源,项目则配置具体业务页面,项目可通过覆盖框架资源实现个性化。关键步骤包括:自定义静态资源访问路径、解决图标与字体文件访问问题、设定自定义欢迎页面和页面图标,以及确保项目能正确访问框架静态资源。通过扫描jar包、解压和拷贝资源到项目目录,实现了框架静态资源的动态加载。此外,调整静态资源访问优先级,保证正确加载。最终实现支持jar和war包的项目结构优化。
49 4
|
15天前
|
Linux 编解码 Python
FFmpeg开发笔记(二十四)Linux环境给FFmpeg集成AV1的编解码器
AV1是一种高效免费的视频编码标准,由AOM联盟制定,相比H.265压缩率提升约27%。各大流媒体平台倾向使用AV1。本文介绍了如何在Linux环境下为FFmpeg集成AV1编解码库libaom、libdav1d和libsvtav1。涉及下载源码、配置、编译和安装步骤,包括设置环境变量以启用这三个库。
37 3
FFmpeg开发笔记(二十四)Linux环境给FFmpeg集成AV1的编解码器