• [springboot专栏]缓存雪崩、穿透、击穿的代码级解决方案


    文章目录

    一、缓存使用的若干问题

    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  # 缓存到期时间,默认不主动删除永远不到期
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    三、自定义缓存到期时间

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

    本专栏是一个连贯的系列专栏,如果存在看不懂的代码,请在本专栏中按自然排序查看之前的2-3篇文章。

    在这里插入图片描述

    RedisCacheManager构造函数包含三个参数

    • RedisCacheWriter这个在之前的章节我们就配置过
    • RedisCacheConfiguration defaultCacheConfiguration 这个是默认的全局配置,针对所有缓存
    • Map 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 ttlmap;  //lombok提供getset方法
    
        //根据ttlmap的属性装配结果,个性化RedisCacheConfiguration
        private Map getRedisCacheConfigurationMap(RedisTemplate redisTemplate) {
            Map redisCacheConfigurationMap = new HashMap<>();
    
            for(Map.Entry 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));
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

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

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

    caching:
      ttlmap:
        article: 10
        xxx: 20
        yyy: 50
    
    • 1
    • 2
    • 3
    • 4
    • 5

    先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

  • 相关阅读:
    【Python程序设计】基于Flask的音乐在线网站/系统/平台
    搭建Java开发环境
    设计模式 行为型模式 - 迭代器模式(八)
    小班中班,随机10以内加法练习题,A4纸可直接打印
    数据结构与算法:树
    机器学习-集成算法
    数据结构-链式二叉树
    小侃设计模式(十四)-职责链模式
    MybatisPLUS,根据时间作为条件进行查询
    【浅学Java】Bean的作用域和生命周期
  • 原文地址:https://blog.csdn.net/m0_67393619/article/details/126080519