Spring Boot 项目中 Redis 常见问题及解决方案

目录

  1. 缓存穿透
  2. 缓存雪崩
  3. 缓存击穿
  4. [Redis 连接池耗尽](#Redis 连接池耗尽)
  5. [Redis 序列化问题](#Redis 序列化问题)
  6. 总结

1. 缓存穿透

问题描述

缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,请求会直接打到数据库上,导致数据库压力过大。

解决方案

  1. 缓存空值:即使查询的数据不存在,也将空值缓存起来,并设置一个较短的过期时间。
  2. 布隆过滤器:在查询缓存之前,先通过布隆过滤器判断数据是否存在。

示例代码

java 复制代码
@Service
public class UserService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public User getUserById(Long id) {
        String key = "user:" + id;
        // 从缓存中获取数据
        User user = (User) redisTemplate.opsForValue().get(key);
        if (user != null) {
            return user;
        }

        // 缓存中不存在,查询数据库
        user = userRepository.findById(id).orElse(null);
        if (user == null) {
            // 缓存空值,设置较短的过期时间
            redisTemplate.opsForValue().set(key, null, 60, TimeUnit.SECONDS);
        } else {
            // 缓存查询结果
            redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
        }
        return user;
    }
}

2. 缓存雪崩

问题描述

缓存雪崩是指大量缓存数据在同一时间失效,导致所有请求都打到数据库上,造成数据库压力过大甚至崩溃。

解决方案

  1. 设置不同的过期时间:为缓存数据设置随机的过期时间,避免大量缓存同时失效。
  2. 使用分布式锁:在缓存失效时,使用分布式锁保证只有一个线程去加载数据。

示例代码

java 复制代码
@Service
public class ProductService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    public Product getProductById(Long id) {
        String key = "product:" + id;
        Product product = (Product) redisTemplate.opsForValue().get(key);
        if (product != null) {
            return product;
        }

        // 使用分布式锁防止缓存击穿
        RLock lock = redissonClient.getLock("lock:" + key);
        try {
            lock.lock();
            // 双重检查,防止其他线程已经加载了数据
            product = (Product) redisTemplate.opsForValue().get(key);
            if (product != null) {
                return product;
            }

            // 查询数据库
            product = productRepository.findById(id).orElse(null);
            if (product != null) {
                // 设置随机的过期时间
                int expireTime = 3600 + new Random().nextInt(600); // 1小时 + 随机10分钟
                redisTemplate.opsForValue().set(key, product, expireTime, TimeUnit.SECONDS);
            }
        } finally {
            lock.unlock();
        }
        return product;
    }
}

3. 缓存击穿

问题描述

缓存击穿是指某个热点数据在缓存中失效后,大量请求同时打到数据库上,导致数据库压力过大。

解决方案

  1. 使用互斥锁:在缓存失效时,使用互斥锁保证只有一个线程去加载数据。
  2. 永不过期策略:对热点数据设置永不过期,通过后台任务定期更新缓存。

示例代码

java 复制代码
@Service
public class HotDataService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public String getHotData() {
        String key = "hot_data";
        String data = (String) redisTemplate.opsForValue().get(key);
        if (data != null) {
            return data;
        }

        // 使用 Redis 的 SETNX 实现互斥锁
        String lockKey = "lock:" + key;
        boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS);
        if (locked) {
            try {
                // 双重检查
                data = (String) redisTemplate.opsForValue().get(key);
                if (data != null) {
                    return data;
                }

                // 模拟从数据库加载热点数据
                data = loadHotDataFromDB();
                redisTemplate.opsForValue().set(key, data, 1, TimeUnit.HOURS);
            } finally {
                // 释放锁
                redisTemplate.delete(lockKey);
            }
        } else {
            // 未获取到锁,等待重试
            try {
                Thread.sleep(100);
                return getHotData(); // 重试
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        return data;
    }

    private String loadHotDataFromDB() {
        // 模拟数据库查询
        return "hot_data_from_db";
    }
}

4. Redis 连接池耗尽

问题描述

在高并发场景下,Redis 连接池可能会被耗尽,导致请求失败。

解决方案

  1. 增加连接池大小:根据实际需求调整连接池的最大连接数。
  2. 优化连接使用:确保每次操作 Redis 后及时释放连接。

示例代码

application.yml 中配置连接池:

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
    lettuce:
      pool:
        max-active: 50  # 最大连接数
        max-idle: 10   # 最大空闲连接数
        min-idle: 5    # 最小空闲连接数

5. Redis 序列化问题

问题描述

默认情况下,Spring Boot 使用 JdkSerializationRedisSerializer 进行序列化,可能导致存储的数据不易阅读或兼容性问题。

解决方案

使用更高效的序列化方式,如 Jackson2JsonRedisSerializerStringRedisSerializer

示例代码

java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // 使用 Jackson2JsonRedisSerializer 序列化值
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        template.setValueSerializer(serializer);
        template.setHashValueSerializer(serializer);

        // 使用 StringRedisSerializer 序列化键
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        return template;
    }
}

6. 总结

在 Spring Boot 项目中使用 Redis 时,可能会遇到缓存穿透、缓存雪崩、缓存击穿、连接池耗尽以及序列化等问题。通过合理的缓存策略、分布式锁、连接池配置和序列化方式,可以有效解决这些问题,提升系统的稳定性和性能。希望本文的解决方案和示例代码能帮助到你!

相关推荐
Mahir089 小时前
Spring 循环依赖深度解密:从问题本质到三级缓存源码级解析
java·后端·spring·缓存·面试·循环依赖·三级缓存
曲幽9 小时前
我用了FastApiAdmin后,连夜把踩过的坑都整理出来了
redis·python·postgresql·vue3·fastapi·web·sqlalchemy·admin·fastapiadmin
IT_陈寒13 小时前
Redis缓存击穿把我整不会了,原来还有这手操作
前端·人工智能·后端
kyriewen14 小时前
面试官让我查各部门工资最高的员工,我用AI三秒写出窗口函数,他愣了
后端·mysql·面试
文心快码BaiduComate14 小时前
干货|Comate Harness Engineering工程实践指南
前端·后端·程序员
光辉GuangHui14 小时前
Agent Skill 也需要测试:如何搭建 Skill 评估框架
前端·后端·llm
我是谁的程序员14 小时前
Mac 上生成 AppStoreInfo.plist 文件,App Store 上架
后端·ios
irving同学4623814 小时前
Node 后端实战:JWT 认证与生产级错误处理
前端·后端
Master_Azur14 小时前
单元测试——Junit单元测试框架
后端
用户83562907805114 小时前
使用 Python 进行 Word 邮件合并
后端