SpringBoot中如何解决Redis的缓存穿透、缓存击穿、缓存雪崩?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: SpringBoot中如何解决Redis的缓存穿透、缓存击穿、缓存雪崩?

什么是 Redis 缓存穿透、缓存击穿、缓存雪崩?

在使用 Redis 缓存时,可能会遇到一些缓存问题,最常见的包括缓存穿透、缓存击穿和缓存雪崩。

1. 缓存穿透

缓存穿透指的是在缓存中没有找到需要的值,每次请求都会访问数据库,而由于数据库中也不存在需要的数据,导致每次请求返回的结果都为空,从而浪费了大量的服务端资源。

这种情况可以通过添加布隆过滤器(BloomFilter)进行处理,将所有可能的查询参数哈希后存储起来,每次查询前先判断哈希值是否存在于布隆过滤器中,若不在则直接返回空结果。

2. 缓存击穿

缓存击穿指的是一个原本存在的 key,在缓存失效的一刹那,同时有大量的并发请求过来,这些请求发现缓存中不存在该 key,于是就直接请求了数据库,从而导致了数据库瞬时压力过大甚至宕机的情况。

这种情况可以通过为热点数据设置永不过期的方式解决,一般会使用 Redis 的 setnx(SET if Not eXists)命令,将缓存数据永久保存在 Redis 中。

3. 缓存雪崩

缓存雪崩指的是缓存中大量的 key,在同一时刻失效,导致大量的请求直接打到了数据库,从而导致数据库瞬时压力过大甚至宕机的情况。

这种情况可以通过加入一个随机过期时间解决,不同的 key 分别设置不同的过期时间来保证不会在同一时间失效。也可以使用 Redis Cluster 技术对 Redis 数据库进行集群化部署,避免单点故障。

SpringBoot 中如何解决 Redis 缓存穿透、缓存击穿、缓存雪崩?

在 SpringBoot 中,我们可以通过配置 RedisTemplate 来实现 Redis 缓存的操作。同时,Spring 提供了 CacheManager 和 Cache 接口用于管理缓存。具体方案如下:

1. 解决 Redis 缓存穿透

1.1 添加布隆过滤器

首先,我们需要在项目中添加布隆过滤器,这里我们使用 Google Guava 库提供的 BloomFilter 实现:

@Bean
public BloomFilter<String> initBloomFilter() {
   
    int expectedInsertions = 1000000;
    double fpp = 0.001;
    return BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
}

这里的 expectedInsertions 是预计添加的元素个数,fpp 表示期望的误差率。可以通过调整这两个参数来控制 BloomFilter 的性能和空间占用。

然后,在查询缓存时,我们需要先将查询参数进行哈希操作并判断是否存在于布隆过滤器中:

@Autowired
private BloomFilter<String> bloomFilter;

public Object query(String key) {
   
    // 先判断 key 是否存在于布隆过滤器中
    if (!bloomFilter.mightContain(key)) {
   
        return null;
    }
    // 查询缓存
    ValueOperations<String, Object> operations = redisTemplate.opsForValue();
    Object result = operations.get(key);
    // 如果缓存中没有找到,则查询数据库
    if (result == null) {
   
        // 查询数据库
        result = queryFromDB(key);
        // 将查询结果加入缓存,并设置过期时间
        if (result != null) {
   
            operations.set(key, result, 5, TimeUnit.MINUTES);
        }
    }
    return result;
}

1.2 添加空值缓存

另外,由于缓存穿透可能会导致大量的请求直接打到数据库,因此我们还可以在缓存中添加空值来避免重复查询。当查询的 key 对应的 value 为 null 时,我们可以将其缓存到 Redis 中,并设置一个较短的过期时间:

public Object query(String key) {
   
    // 先从缓存中查询
    ValueOperations<String, Object> operations = redisTemplate.opsForValue();
    Object result = operations.get(key);
    // 如果缓存中没有找到,则查询数据库
    if (result == null) {
   
        // 查询数据库
        result = queryFromDB(key);
        // 将查询结果加入缓存,并设置过期时间
        if (result != null) {
   
            operations.set(key, result, 5, TimeUnit.MINUTES);
        } else {
   
            // 缓存空值,避免重复查询
            operations.set(key, "", 1, TimeUnit.MINUTES);
        }
    }
    // 如果查询结果是空字符串,则返回 null
    return "".equals(result) ? null : result;
}

2. 解决 Redis 缓存击穿

为了避免缓存击穿,我们可以将一些热点数据永久保存在 Redis 中。同时,我们需要注意设置合适的过期时间,以免占用过多的内存。

@Cacheable(value = "user", key = "#id", unless = "#result == null")
public User getUser(Long id) {
   
    // 先从缓存中查询
    User user = userDao.selectById(id);
    // 如果缓存中没有找到,则查询数据库
    if (user == null) {
   
        // 查询数据库
        user = queryFromDB(id);
        // 将查询结果加入缓存,并永不过期
        if (user != null) {
   
            redisTemplate.opsForValue().set("user:" + id, user);
        }
    }
    return user;
}

3. 解决 Redis 缓存雪崩

为了避免缓存雪崩,我们可以在设置缓存时加入一个随机的过期时间,这样可以将原本同时失效的缓存数据错开。

@Cacheable(value = "user", key = "#id", unless = "#result == null")
public User getUser(Long id) {
   
    // 先从缓存中查询
    User user = redisTemplate.opsForValue().get("user:" + id);
    // 如果缓存中没有找到,则查询数据库
    if (user == null) {
   
        // 查询数据库
        user = queryFromDB(id);
        // 将查询结果加入缓存,并设置随机过期时间,避免同时失效
        if (user != null) {
   
            long expireTime = new Random().nextInt(300) + 600;
            redisTemplate.opsForValue().set("user:" + id, user, expireTime, TimeUnit.SECONDS);
        }
    }
    return user;
}

总结

Redis 是一个高性能的缓存工具,在处理大量数据时非常有用。但是,当面对大规模缓存时,可能会产生一些缓存问题,如缓存穿透、缓存击穿和缓存雪崩等。针对这些问题,我们可以使用 BloomFilter 等技术来优化查询,也可以设置永不过期、随机过期时间等方式来避免缓存击穿和缓存雪崩。同时,在 SpringBoot 中,我们可以使用 CacheManager 和 Cache 接口来管理缓存,使得缓存的操作更加简单方便。

目录
相关文章
|
5月前
|
缓存 NoSQL 关系型数据库
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
美团面试:MySQL有1000w数据,redis只存20w的数据,如何做 缓存 设计?
|
3月前
|
缓存 数据库连接 数据库
缓存三剑客(穿透、击穿、雪崩)
缓存穿透指查询数据库和缓存中都不存在的数据,导致请求直接冲击数据库。解决方案包括缓存空对象和布隆过滤器。缓存击穿是大量请求访问同一个失效的热点数据,使数据库瞬间压力剧增,解决方法有提前预热、设置永不过期、加锁限流等。缓存雪崩是大量key同时失效,导致所有请求直达数据库,可通过引入随机过期时间缓解。三者分别对应单点爆破、全面崩塌等问题,需根据场景选择合适策略优化系统性能与稳定性。
225 0
|
3月前
|
存储 缓存 NoSQL
如何解决缓存击穿?
缓存击穿是指热点数据失效时大量请求直接冲击数据库,可能导致系统崩溃。解决方案包括:永不过期策略避免缓存失效瞬间的穿透;互斥锁控制并发访问;热点预热提前刷新缓存;熔断降级在数据库压力大时返回默认值;二级缓存降低Redis压力。实际中常组合使用多种方案,如热点预热+互斥锁+熔断降级,以提升系统稳定性与性能。
321 0
|
20天前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
110 1
Redis专题-实战篇二-商户查询缓存
|
26天前
|
NoSQL Java 调度
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
分布式锁是分布式系统中用于同步多节点访问共享资源的机制,防止并发操作带来的冲突。本文介绍了基于Spring Boot和Redis实现分布式锁的技术方案,涵盖锁的获取与释放、Redis配置、服务调度及多实例运行等内容,通过Docker Compose搭建环境,验证了锁的有效性与互斥特性。
分布式锁与分布式锁使用 Redis 和 Spring Boot 进行调度锁(不带 ShedLock)
|
20天前
|
缓存 NoSQL 关系型数据库
Redis缓存和分布式锁
Redis 是一种高性能的键值存储系统,广泛用于缓存、消息队列和内存数据库。其典型应用包括缓解关系型数据库压力,通过缓存热点数据提高查询效率,支持高并发访问。此外,Redis 还可用于实现分布式锁,解决分布式系统中的资源竞争问题。文章还探讨了缓存的更新策略、缓存穿透与雪崩的解决方案,以及 Redlock 算法等关键技术。
|
2月前
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
206 0
|
3月前
|
缓存 NoSQL 数据库
什么是缓存击穿
缓存击穿是指热点缓存key突然失效,导致大量并发请求直接冲击数据库,造成巨大压力。常见于高并发场景,如热门商品信息失效时。解决方法包括设置热点key永不过期、使用分布式锁、预热数据、熔断降级等,以保障系统稳定性。
478 0
|
4月前
|
机器学习/深度学习 数据采集 人机交互
springboot+redis互联网医院智能导诊系统源码,基于医疗大模型、知识图谱、人机交互方式实现
智能导诊系统基于医疗大模型、知识图谱与人机交互技术,解决患者“知症不知病”“挂错号”等问题。通过多模态交互(语音、文字、图片等)收集病情信息,结合医学知识图谱和深度推理,实现精准的科室推荐和分级诊疗引导。系统支持基于规则模板和数据模型两种开发原理:前者依赖人工设定症状-科室规则,后者通过机器学习或深度学习分析问诊数据。其特点包括快速病情收集、智能病症关联推理、最佳就医推荐、分级导流以及与院内平台联动,提升患者就诊效率和服务体验。技术架构采用 SpringBoot+Redis+MyBatis Plus+MySQL+RocketMQ,确保高效稳定运行。
279 0
|
缓存 NoSQL Java
Redis缓存穿透、缓存雪崩、redis并发问题分析
把redis作为缓存使用已经是司空见惯,但是使用redis后也可能会碰到一系列的问题,尤其是数据量很大的时候,经典的几个问题如下: (一)缓存和数据库间数据一致性问题 分布式环境下(单机就不用说了)非常容易出现缓存和数据库间的数据一致性问题,针对这一点的话,只能说,如果你的项目对缓存的要求是强一致性的,那么请不要使用缓存。
1927 0