Redis缓存雪崩穿透等解决方案

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 本文讨论了缓存使用中可能出现的问题及其解决方案。首先,缓存穿透是指查询数据库中不存在的数据,导致请求频繁到达数据库。解决方法包括数据校验、缓存空值和使用BloomFilter。其次,缓存击穿是大量请求同一失效缓存项,可采取监控、限流或加锁策略。再者,缓存雪崩是大量缓存同时失效,引发数据库压力。应对措施是避免同一失效时间,分散缓存过期。接着,文章介绍了Spring Boot中Redis缓存的配置,包括缓存null值以防止穿透,并展示了自定义缓存过期时间的实现,以避免雪崩效应。最后,提供了在`application.yml`中配置不同缓存项的个性化过期时间的方法。

2000元阿里云代金券免费领取,2核4G云服务器仅664元/3年,新老用户都有优惠,立即抢购>>>


阿里云采购季(云主机223元/3年)活动入口:请点击进入>>>,


阿里云学生服务器(9.5元/月)购买入口:请点击进入>>>,

一、缓存使用的若干问题
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分钟完成数据库上云实战!/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5天前
|
缓存 NoSQL 关系型数据库
【Redis】Redis 缓存重点解析
【Redis】Redis 缓存重点解析
13 0
|
5天前
|
缓存 NoSQL 关系型数据库
【Redis】Redis作为缓存
【Redis】Redis作为缓存
7 0
|
5天前
|
存储 缓存 监控
利用Redis构建高性能的缓存系统
在现今高负载、高并发的互联网应用中,缓存系统的重要性不言而喻。Redis,作为一款开源的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息代理。本文将深入探讨Redis的核心特性,以及如何利用Redis构建高性能的缓存系统,并通过实际案例展示Redis在提升系统性能方面的巨大潜力。
|
5天前
|
存储 缓存 NoSQL
【技术分享】求取列表需求的redis缓存方案
【技术分享】求取列表需求的redis缓存方案
15 0
|
5天前
|
缓存 NoSQL 安全
Redis经典问题:缓存击穿
本文探讨了高并发系统中Redis缓存击穿的问题及其解决方案。缓存击穿指大量请求同一未缓存数据,导致数据库压力过大。为解决此问题,可以采取以下策略:1) 热点数据永不过期,启动时加载并定期异步刷新;2) 写操作加互斥锁,保证并发安全并设置查询失败返回默认值;3) 预期热点数据直接加缓存,系统启动时加载并设定合理过期时间;4) 手动操作热点数据上下线,通过界面控制缓存刷新。这些方法能有效增强系统稳定性和响应速度。
221 0
|
5天前
|
负载均衡 监控 NoSQL
Redis的几种主要集群方案
【5月更文挑战第15天】Redis集群方案包括主从复制(基础,读写分离,手动故障恢复)、哨兵模式(自动高可用,自动故障转移)和Redis Cluster(官方分布式解决方案,自动分片、容错和扩展)。此外,还有Codis、Redisson和Twemproxy等工具用于代理分片和负载均衡。选择方案需考虑应用场景、数据量和并发需求,权衡可用性、性能和扩展性。
38 2
|
5天前
|
存储 监控 负载均衡
保证Redis的高可用性是一个涉及多个层面的任务,主要包括数据持久化、复制与故障转移、集群化部署等方面
【5月更文挑战第15天】保证Redis高可用性涉及数据持久化、复制与故障转移、集群化及优化策略。RDB和AOF是数据持久化方法,哨兵模式确保故障自动恢复。Redis Cluster实现分布式部署,提高负载均衡和容错性。其他措施包括身份认证、多线程、数据压缩和监控报警,以增强安全性和稳定性。通过综合配置与监控,可确保Redis服务的高效、可靠运行。
27 2
|
5天前
|
存储 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(下)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
20 1
|
5天前
|
监控 NoSQL Redis
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群(上)
Redis源码、面试指南(5)多机数据库、复制、哨兵、集群
34 0
|
5天前
|
存储 监控 NoSQL
Redis哨兵&分片集群
Redis哨兵&分片集群
23 0
http://www.vxiaotou.com