Spring Boot 3 集成 Redis 实现接口缓存,让你的服务性能翻倍
Spring Boot 3 集成 Redis 实现接口缓存让你的服务性能翻倍一、引言在实际项目开发中我们经常会遇到某些接口响应速度慢的问题。比如查询商品详情、获取用户信息等接口每次请求都需要查询数据库在高并发场景下就会成为性能瓶颈。而缓存正是解决这类问题的利器。本文将带你从零开始在 Spring Boot 3 项目中集成 Redis并使用 Spring Cache 注解优雅地实现接口缓存让你的服务性能显著提升。二、为什么选择 Redis 做缓存在 Java 生态中缓存方案有很多比如 Caffeine、Ehcache 等本地缓存。但 Redis 有以下不可替代的优势分布式共享多个服务实例可以共享同一份缓存数据保证数据一致性。持久化支持Redis 支持 RDB 和 AOF 持久化重启后数据不丢失。丰富的数据结构除了 String还支持 Hash、List、Set 等满足不同场景。高可用支持主从复制、哨兵模式和集群模式。Spring Boot 对 Redis 和 Cache 抽象提供了开箱即用的支持集成成本非常低。三、核心原理Spring Cache 抽象Spring Cache 是 Spring 框架提供的一套缓存抽象层它定义了几个核心注解| 注解 | 说明 | |------|------| |Cacheable| 先查缓存有则直接返回无则执行方法并将结果存入缓存 | |CachePut| 总是执行方法并将结果更新到缓存 | |CacheEvict| 执行方法后删除缓存 |这些注解背后的流程以Cacheable为例请求到达时Spring AOP 拦截被Cacheable标注的方法。根据注解配置的cacheNames和key生成缓存 key。去 Redis 中查询该 key 是否存在。存在则直接返回缓存值方法体不执行。不存在则执行方法体将返回值序列化后存入 Redis。底层由CacheManager和Cache接口完成具体操作我们只需配置好 Redis 作为缓存存储即可。四、实践步骤4.1 环境准备JDK 17Spring Boot 3.2Redis 7.x本地或 DockerMaven快速启动 RedisDocker 方式docker run -d --name redis -p 6379:6379 redis:7-alpine4.2 添加依赖在pom.xml中添加!-- Spring Boot Redis Starter -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency !-- Spring Cache Starter通常已被 spring-boot-starter-web 间接引入 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency !-- Jackson 序列化Spring Boot Web 默认包含 -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId /dependency4.3 配置文件application.ymlspring: data: redis: host: localhost port: 6379 timeout: 3000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 24.4 Redis 缓存配置类这是最关键的一步我们需要自定义RedisCacheManager设置 JSON 序列化方式和默认过期时间import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; Configuration EnableCaching public class RedisCacheConfig { Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { // 配置 ObjectMapper使其在序列化时带上类型信息 ObjectMapper mapper new ObjectMapper(); mapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY ); GenericJackson2JsonRedisSerializer serializer new GenericJackson2JsonRedisSerializer(mapper); RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() // 设置默认过期时间为 30 分钟 .entryTtl(Duration.ofMinutes(30)) // 禁止缓存 null 值 .disableCachingNullValues() // key 使用 String 序列化 .serializeKeysWith( RedisSerializationContext.SerializationPair .fromSerializer(new StringRedisSerializer()) ) // value 使用 JSON 序列化 .serializeValuesWith( RedisSerializationContext.SerializationPair .fromSerializer(serializer) ); return RedisCacheManager.builder(factory) .cacheDefaults(config) .transactionAware() .build(); } }说明activateDefaultTyping是为了在 JSON 中保存 Java 类型信息反序列化时能正确还原对象。如果你的缓存值都是同一种类型如 String也可以直接使用StringRedisSerializer性能更好。4.5 模拟业务 Serviceimport com.example.demo.entity.Product; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; Slf4j Service public class ProductService { /** * 根据 ID 查询商品 * cacheNames缓存命名空间相当于 Redis 中的 key 前缀 * key缓存的 key支持 SpEL 表达式#id 表示取参数 id 的值 */ Cacheable(cacheNames product, key #id) public Product getById(Long id) { // 模拟从数据库查询的耗时操作 log.info(查询数据库商品 ID{}, id); slowQuery(); return new Product(id, 商品- id, 99.99); } /** * 更新商品同时清除对应缓存 */ CacheEvict(cacheNames product, key #product.id) public Product update(Product product) { log.info(更新商品ID{}, product.getId()); // 模拟数据库更新 return product; } private void slowQuery() { try { Thread.sleep(2000); } catch (InterruptedException ignored) { } } }4.6 控制器层import com.example.demo.entity.Product; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; RestController RequestMapping(/api/products) RequiredArgsConstructor public class ProductController { private final ProductService productService; GetMapping(/{id}) public Product getProduct(PathVariable Long id) { return productService.getById(id); } PutMapping public Product updateProduct(RequestBody Product product) { return productService.update(product); } }4.7 实体类import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; Data NoArgsConstructor AllArgsConstructor public class Product implements Serializable { private Long id; private String name; private Double price; }注意缓存对象必须实现Serializable接口否则序列化时会报错。4.8 验证效果启动项目后调用GET /api/products/1第一次请求控制台打印 查询数据库商品 ID1耗时约 2 秒。第二次请求控制台无日志直接返回缓存数据耗时在毫秒级。调用 PUT 更新缓存被清除下次查询会重新走数据库。在 Redis 中执行KEYS product::*可以看到缓存的 key。五、常见问题与注意事项1. 缓存穿透当查询一个不存在的数据时每次都会穿透缓存去查数据库。解决方案缓存空值设置较短过期时间或使用布隆过滤器。2. 缓存雪崩大量缓存同时过期导致请求全部打到数据库。解决方案给过期时间加上随机值避免集中失效。3. 缓存击穿热点数据过期瞬间大量请求同时查询数据库。解决方案使用互斥锁只让一个线程去查询并重建缓存。4. SpEL Key 表达式key支持 SpEL 表达式常用写法#id取方法参数名为 id 的值#result.id取返回值的 id仅用于CachePut/CacheEvict#root.args[0]取第一个参数六、总结本文介绍了 Spring Boot 3 集成 Redis 实现接口缓存的完整流程引入spring-boot-starter-data-redis和spring-boot-starter-cache依赖配置 Redis 连接信息自定义RedisCacheManager设置 JSON 序列化和过期时间使用Cacheable、CacheEvict等注解简化缓存逻辑。通过这套方案你可以在不侵入业务代码的情况下快速为接口加上缓存能力。对于读多写少的场景效果立竿见影。建议在实际项目中根据业务需求合理设置缓存过期时间并做好缓存穿透、雪崩、击穿的防护。