Spring Boot整合Redis案例

wen java案例 3

本文目录导读:

Spring Boot整合Redis案例

  1. 文章标题:Spring Boot整合Redis实战指南:从零搭建高性能缓存体系
  2. 目录导读
  3. 为什么选择Redis?—— 缓存王者与现代应用痛点
  4. 环境准备:Spring Boot 3.x + Redis 7.x 依赖与配置
  5. 实战案例一:基于StringRedisTemplate的字符串缓存(商品详情)
  6. 实战案例二:基于RedisTemplate的JSON序列化(用户会话)
  7. 实战案例三:分布式锁与缓存穿透/击穿/雪崩的防守策略
  8. 性能调优与监控:连接池、Key过期策略与Lettuce调参
  9. 常见问题FAQ(高频面试/踩坑问答)

Spring Boot整合Redis实战指南:从零搭建高性能缓存体系


目录导读

  1. 为什么选择Redis?—— 缓存王者与现代应用痛点
  2. 环境准备:Spring Boot 3.x + Redis 7.x 依赖与配置
  3. 实战案例一:基于StringRedisTemplate的字符串缓存(商品详情)
  4. 实战案例二:基于RedisTemplate的JSON序列化(用户会话)
  5. 实战案例三:分布式锁与缓存穿透/击穿/雪崩的防守策略
  6. 性能调优与监控:连接池、Key过期策略与Lettuce调参
  7. 常见问题FAQ(高频面试/踩坑问答)

为什么选择Redis?—— 缓存王者与现代应用痛点

在微服务与高并发场景下,数据库(如MySQL)的磁盘I/O成为瓶颈,Redis作为内存中的数据结构存储系统,以微秒级响应丰富的数据类型(String、Hash、List、Set、ZSet)和原子性操作成为缓存首选,Spring Boot官方提供了spring-boot-starter-data-redis,底层默认采用Lettuce(基于Netty,线程安全)而非Jedis,Lettuce支持异步与集群,更适合生产环境。

痛点解决:缓存热点数据(如商品、用户信息)可将QPS提升10-100倍,同时配合持久化(RDB/AOF)保证数据安全性。


环境准备:Spring Boot 3.x + Redis 7.x 依赖与配置

引入Maven依赖(pom.xml)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId> <!-- 连接池 -->
</dependency>

application.yml配置(重点:连接池与序列化器)

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: yourpass   # 生产必填
      timeout: 3000ms
      lettuce:
        pool:
          max-active: 8      # 最大连接数
          max-idle: 8        # 最大空闲
          min-idle: 0
          max-wait: -1ms     # 无限等待

关键点:若使用Spring Boot 2.x,前缀为spring.redis.*;3.x时改为spring.data.redis.*,务必配置连接池,防止高并发下连接耗尽。


实战案例一:基于StringRedisTemplate的字符串缓存(商品详情)

场景:查询商品ID为1001的信息,先查缓存,未命中则查数据库并回填。

@Service
public class ProductService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private ProductMapper productMapper; // MyBatis示例
    public Product getProduct(Long id) {
        // 1. 从缓存获取
        String json = stringRedisTemplate.opsForValue().get("product:" + id);
        if (json != null) {
            return JSON.parseObject(json, Product.class); // Fastjson2
        }
        // 2. 缓存未命中,查数据库
        Product product = productMapper.selectById(id);
        // 3. 回填缓存,设置过期时间(如30分钟)
        stringRedisTemplate.opsForValue().set("product:" + id, 
              JSON.toJSONString(product), 30, TimeUnit.MINUTES);
        return product;
    }
}

问答:为什么用StringRedisTemplate?它默认使用String序列化,Key和Value都是字符串,适合简单场景,且不会出现乱码,对于复杂对象,需手动转JSON。


实战案例二:基于RedisTemplate的JSON序列化(用户会话)

问题:直接使用RedisTemplate存对象会产生JdkSerializationRedisSerializer,导致存进去的是二进制乱码,且不可读。

解决方案:自定义RedisConfig,改用GenericJackson2JsonRedisSerializer

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // key使用String序列化
        template.setKeySerializer(RedisSerializer.string());
        // value使用JSON序列化(存储JSON字符串,便于其他系统读取)
        GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(RedisSerializer.string());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        return template;
    }
}

实战:存储用户会话对象UserSession。

redisTemplate.opsForValue().set("session:user:" + userId, userSession, 2, TimeUnit.HOURS);

问答:为什么不直接用Jackson2JsonRedisSerializer?它需要指定目标类型,且反序列化时可能丢失类型信息。GenericJackson2JsonRedisSerializer会保存@class字段,支持多态,但有安全风险(需配置白名单),生产更推荐Fastjson2或Protobuf。


实战案例三:分布式锁与缓存穿透/击穿/雪崩的防守策略

缓存穿透(查询不存在的数据)

方案:缓存空值(null)或布隆过滤器,实例如缓存空值:

String json = ops.get(key);
if (json == null) {
    Product p = db.query(id);
    if (p == null) {
        ops.set(key, "", 5, TimeUnit.MINUTES); // 缓存空串
    }
}

缓存击穿(热点Key过期瞬间大量请求打到DB)

方案:互斥锁(分布式锁),基于Redis的SETNX实现:

String lockKey = "lock:product:" + id;
Boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
    try {
        // 查库并回填缓存
    } finally {
        redisTemplate.delete(lockKey);
    }
} else {
    Thread.sleep(100); // 等待重试
}

生产建议用Redisson框架,自带看门狗续期机制。

缓存雪崩(大量Key同时过期)

方案:过期时间加随机值(例如300s + random(0~60)),或者将热点数据设为永不过期,但后台异步更新。


性能调优与监控:连接池、Key过期策略与Lettuce调参

  • 连接池大小max-active=8通常够用,但压测后如出现连接等待,可调至16,注意每个线程占用一个连接,Redis操作需快速释放。
  • Key过期策略:生产建议用惰性删除+定期删除maxmemory-policy设置为allkeys-lru(或volatile-lru)防止内存满。
  • Lettuce调参:开启共享连接(share-native-connection: true),可减少连接数,关注io.netty.eventLoopThreads
  • 监控:引入actuator,开启redis健康检查;使用redis-cli --stat或Prometheus监控hit_rate(缓存命中率)。

常见问题FAQ(高频面试/踩坑问答)

Q1:Spring Boot 3.x里Redis连接报错Unable to connect to Redis 答:检查application.yml中配置前缀是否更新为spring.data.redis,确认Redis服务端关闭保护模式(protected-mode no)或设置密码。

Q2:为什么使用RedisTemplate后Key变成\xac\xed\x00\x05t... 答:这是Jdk序列化的典型乱码,重新定义RedisTemplate的Key序列化为StringRedisSerializer,见第四章节配置。

Q3:缓存和数据库双写一致性如何保证? 答:推荐先更新数据库,再删除缓存(Cache Aside Pattern),删除失败可用消息队列重试,或使用Canal订阅binlog异步删除。

Q4:Redis分布式锁为什么要加过期时间?如果业务没执行完锁就释放了怎么办? 答:必须加过期时间防止死锁,但到期未执行完会导致并发问题,解决方案是Redisson看门狗,在锁释放前续期(默认每10秒续期到30秒)。

Q5:如何测试缓存是否生效? 答:集成测试时,先插入数据,再用redisTemplate.getExpire(key)查看TTL;或查询后打点日志判断是否走了缓存。

Q6:Redis集群下,Key如何分布? 答:使用Hash Slot(16384槽),Spring Boot直连集群需配置spring.redis.cluster.nodes,Lettuce自动支持拓扑刷新,但注意keys命令禁止使用。


Spring Boot整合Redis不仅仅是引入依赖和调用API,更深层的是序列化设计、异常兜底、高并发防御,通过上述案例,你已能构建一个具备防穿透、防击穿能力的缓存微服务,下一步建议深入RedissonRedis Stream(消息队列)以及缓存预热/双写一致性方案,逐步架构大型系统的心脏。缓存是银弹,但用错地方就是毒药——务必评估数据一致性容忍度。

抱歉,评论功能暂时关闭!