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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容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
相关文章
|
10天前
|
存储 缓存 NoSQL
深入理解Django与Redis的集成实践
深入理解Django与Redis的集成实践
31 0
|
13天前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
49 6
|
10天前
|
NoSQL Java Redis
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
这篇文章介绍了如何使用Spring Boot整合Apache Shiro框架进行后端开发,包括认证和授权流程,并使用Redis存储Token以及MD5加密用户密码。
15 0
shiro学习四:使用springboot整合shiro,正常的企业级后端开发shiro认证鉴权流程。使用redis做token的过滤。md5做密码的加密。
|
13天前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
36 4
|
13天前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
40 3
|
13天前
|
消息中间件 缓存 NoSQL
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
大数据-49 Redis 缓存问题中 穿透、雪崩、击穿、数据不一致、HotKey、BigKey
33 2
|
13天前
|
缓存 分布式计算 NoSQL
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
大数据-47 Redis 缓存过期 淘汰删除策略 LRU LFU 基础概念
33 2
|
6天前
|
NoSQL API Redis
如何使用 C++ 开发 Redis 模块
如何使用 C++ 开发 Redis 模块
|
17天前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
52 1