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

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

大家好,我是飘渺!

今天给大家介绍一下如何在SpringBoot中解决Redis的缓存穿透、缓存击穿、缓存雪崩的问题。


缓存穿透


什么是缓存穿透

缓存穿透指的是一个缓存系统无法缓存某个查询的数据,从而导致这个查询每一次都要访问数据库。

常见的Redis缓存穿透场景包括:

  1. 查询一个不存在的数据:攻击者可能会发送一些无效的查询来触发缓存穿透。
  2. 查询一些非常热门的数据:如果一个数据被访问的非常频繁,那么可能会导致缓存系统无法处理这些请求,从而造成缓存穿透。
  3. 查询一些异常数据:这种情况通常发生在数据服务出现故障或异常时,从而造成缓存系统无法访问相关数据,从而导致缓存穿透。

如何解决

我们可以使用Guava在内存中维护一个布隆过滤器。具体步骤如下:

  1. 添加Guava和Redis依赖:
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>29.0-jre</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 创建一个BloomFilterUtil类,用于在缓存中维护Bloom Filter。
public class BloomFilterUtil {
    // 布隆过滤器的预计容量
    private static final int expectedInsertions = 1000000;
    // 布隆过滤器误判率
    private static final double fpp = 0.001;
    private static BloomFilter<String> bloomFilter = BloomFilter.create(Funnels.stringFunnel(Charset.defaultCharset()), expectedInsertions, fpp);
    /**
     * 向Bloom Filter中添加元素
     */
    public static void add(String key){
        bloomFilter.put(key);
    }
    /**
     * 判断元素是否存在于Bloom Filter中
     */
    public static boolean mightContain(String key){
        return bloomFilter.mightContain(key);
    }
}
  1. 在Controller中查询数据时,先根据请求参数进行Bloom Filter的过滤
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id){
    // 先从布隆过滤器中判断此id是否存在
    if(!BloomFilterUtil.mightContain(id.toString())){
        return null;
    }
    // 查询缓存数据
    String userKey = "user_"+id.toString();
    User user = (User) redisTemplate.opsForValue().get(userKey);
    if(user == null){
        // 查询数据库
        user = userRepository.findById(id).orElse(null);
        if(user != null){
            // 将查询到的数据加入缓存
            redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
        }else{
            // 查询结果为空,将请求记录下来,并在布隆过滤器中添加
            BloomFilterUtil.add(id.toString());
        }
    }
    return user;
}


缓存击穿


什么是缓存击穿

缓存击穿指的是在一些高并发访问下,一个热点数据从缓存中不存在,每次请求都要直接查询数据库,从而导致数据库压力过大,并且系统性能下降的现象。

缓存击穿的原因通常有以下几种:

  1. 缓存中不存在所需的热点数据:当系统中某个热点数据需要被频繁访问时,如果这个热点数据最开始没有被缓存,那么就会导致系统每次请求都需要直接查询数据库,造成数据库负担。
  2. 缓存的热点数据过期:当一个热点数据过期并需要重新缓存时,如果此时有大量请求,那么就会导致所有请求都要直接查询数据库。

如何解决

主要思路 : 在遇到缓存击穿问题时,我们可以在查询数据库之前,先判断一下缓存中是否已有数据,如果没有数据则使用Redis的单线程特性,先查询数据库然后将数据写入缓存中。

  1. 添加Redis依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  1. 在Controller中查询数据时,先从缓存中查询数据,如果缓存中无数据则进行锁操作
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@GetMapping("/user/{id}")
public User getUserById(@PathVariable Long id){
    // 先从缓存中获取值
    String userKey = "user_"+id.toString();
    User user = (User) redisTemplate.opsForValue().get(userKey);
    if(user == null){
        // 查询数据库之前加锁
        String lockKey = "lock_user_"+id.toString();
        String lockValue = UUID.randomUUID().toString();
        try{
            Boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 60, TimeUnit.SECONDS);
            if(lockResult != null && lockResult){
                // 查询数据库
                user = userRepository.findById(id).orElse(null);
                if(user != null){
                    // 将查询到的数据加入缓存
                    redisTemplate.opsForValue().set(userKey, user, 300, TimeUnit.SECONDS);
                }
            }
        }finally{
            // 释放锁
            if(lockValue.equals(redisTemplate.opsForValue().get(lockKey))){
                redisTemplate.delete(lockKey);
            }
        }
    }
    return user;
}


缓存雪崩


什么是缓存雪崩

指缓存中大量数据的失效时间集中在某一个时间段,导致在这个时间段内缓存失效并额外请求数据库查询数据的请求大量增加,从而对数据库造成极大的压力和负荷。

常见的Redis缓存雪崩场景包括:

  1. 缓存服务器宕机:当缓存服务器宕机或重启时,大量的访问请求将直接命中数据库,并在同一时间段内导致大量的数据库查询请求,从而将数据库压力大幅提高。
  2. 缓存数据同时失效:在某个特定时间点,缓存中大量数据的失效时间集中在一起,这些数据会在同一时间段失效,并且这些数据被高频访问,将导致大量的访问请求去查询数据库。
  3. 缓存中数据过期时间设计不合理:当缓存中的数据有效时间过短,且数据集中在同一时期失效时,就容易导致大量的请求直接查询数据库,加剧数据库压力。
  4. 波动式的访问过程:当数据的访问存在波动式特征时,例如输出某些活动物品或促销商品时,将会带来高频的查询请求访问,导致缓存大量失效并产生缓存雪崩。

如何解决

在遇到缓存雪崩时,我们可以使用两种方法:一种是将缓存过期时间分散开,即为不同的数据设置不同的过期时间;另一种是使用Redis的多级缓存架构,通过增加一层代理层来解决。具体步骤如下:

  1. 添加相关依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>
  1. 在application.properties中配置Ehcache缓存
spring.cache.type=ehcache
  1. 创建一个CacheConfig类,用于配置Ehcache:
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public EhCacheCacheManager ehCacheCacheManager(CacheManager cm){
        return new EhCacheCacheManager(cm);
    }
    @Bean
    public CacheManager ehCacheManager(){
        EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
        cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cmfb.setShared(true);
        return cmfb.getObject();
    }
}
  1. 在ehcache.xml中添加缓存配置
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
    updateCheck="true"
    monitoring="autodetect"
    dynamicConfig="true">
    <cache name="userCache" maxEntriesLocalHeap="10000" timeToLiveSeconds="60" timeToIdleSeconds="30"/>
</ehcache>
  1. 在Controller中查询数据时,先从Ehcache缓存中获取,如果缓存中无数据则再从Redis缓存中获取数据
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private CacheManager ehCacheManager;
@GetMapping("/user/{id}")
@Cacheable(value = "userCache", key = "#id")
public User getUserById(@PathVariable Long id){
    // 先从Ehcache缓存中获取
    String userKey = "user_"+id.toString();
    User user = (User) ehCacheManager.getCache("userCache").get(userKey).get();
    if(user == null){
        // 再从Redis缓存中获取
        user = (User) redisTemplate.opsForValue().get(userKey);
        if(user != null){
            ehCacheManager.getCache("userCache").put(userKey, user);
        }
    }
    return user;
}

以上就是使用SpringBoot时如何解决Redis的缓存穿透、缓存击穿、缓存雪崩的常用方法。

相关实践学习
基于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
目录
相关文章
|
11天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
12天前
|
存储 缓存 NoSQL
【赵渝强老师】基于Redis的旁路缓存架构
本文介绍了引入缓存后的系统架构,通过缓存可以提升访问性能、降低网络拥堵、减轻服务负载和增强可扩展性。文中提供了相关图片和视频讲解,并讨论了数据库读写分离、分库分表等方法来减轻数据库压力。同时,文章也指出了缓存可能带来的复杂度增加、成本提高和数据一致性问题。
【赵渝强老师】基于Redis的旁路缓存架构
|
11天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
23 3
|
13天前
|
消息中间件 NoSQL Java
Spring Boot整合Redis
通过Spring Boot整合Redis,可以显著提升应用的性能和响应速度。在本文中,我们详细介绍了如何配置和使用Redis,包括基本的CRUD操作和具有过期时间的值设置方法。希望本文能帮助你在实际项目中高效地整合和使用Redis。
33 1
|
19天前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
32 0
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(一)
数据的存储--Redis缓存存储(一)
|
1月前
|
存储 缓存 NoSQL
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
数据的存储--Redis缓存存储(二)
|
1月前
|
消息中间件 缓存 NoSQL
Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。
【10月更文挑战第4天】Redis 是一个高性能的键值对存储系统,常用于缓存、消息队列和会话管理等场景。随着数据增长,有时需要将 Redis 数据导出以进行分析、备份或迁移。本文详细介绍几种导出方法:1)使用 Redis 命令与重定向;2)利用 Redis 的 RDB 和 AOF 持久化功能;3)借助第三方工具如 `redis-dump`。每种方法均附有示例代码,帮助你轻松完成数据导出任务。无论数据量大小,总有一款适合你。
77 6
|
5天前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
22 5
|
20天前
|
缓存 NoSQL Redis
Redis 缓存使用的实践
《Redis缓存最佳实践指南》涵盖缓存更新策略、缓存击穿防护、大key处理和性能优化。包括Cache Aside Pattern、Write Through、分布式锁、大key拆分和批量操作等技术,帮助你在项目中高效使用Redis缓存。
112 22

热门文章

最新文章

下一篇
无影云桌面