描述:
在现代Java开发中,缓存是提升系统性能的核心手段之一。本文通过实际代码案例,深入解析Spring Cache与Redis的集成原理,结合项目中的ModuleDatabaseInfoService
接口和RedisConfig
配置,探讨如何通过声明式缓存实现高效的数据库访问优化。
一、核心代码解析
1. 服务接口设计(拿查询数据源配置信息举例)
public interface ModuleDatabaseInfoService extends TestableService, DataSourceNameProvider {
@Cacheable(CacheConstant.CACHE_DATASOURCE)
ModuleDatabaseInfo getDataSourceById(String databaseCode);
@CacheEvict(CacheConstant.CACHE_DATASOURCE)
void refreshDataSource(Long databaseId);
}
功能说明 :
getDataSourceById
:通过@Cacheable
注解缓存数据源配置信息,避免重复查询数据库。
refreshDataSource
:通过@CacheEvict
清除指定ID的缓存,确保数据一致性。
缓存名称 :CacheConstant.CACHE_DATASOURCE
定义了缓存的命名空间,用于隔离不同业务场景的缓存。
2. Redis配置核心
@Bean("jsonRedisCacheManager")
@Primary
public CacheManager customCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 1. 配置Jackson序列化器
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(objectMapper2, Object.class);
// 2. 构建Redis缓存配置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
.entryTtl(Duration.ofMinutes(10L)) // 默认10分钟过期
.disableCachingNullValues(); // 禁止缓存空值
// 3. 创建缓存写入器
RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(
redisConnectionFactory,
BatchStrategies.scan(1000) // 使用SCAN命令批量处理
);
// 4. 返回自定义缓存管理器
return new TtlRedisCacheManager(redisCacheWriter, config);
}
关键配置 :
序列化 :使用Jackson2JsonRedisSerializer
将对象序列化为JSON,支持复杂类型(如LocalDateTime
)。
过期时间 :设置全局缓存过期时间为10分钟,避免数据长期滞留。
批量处理 :通过BatchStrategies.scan(1000)
优化大Key集合操作,避免Redis阻塞。
redisConfig类代码片段分享
/**
* @date 2023年09月15日
* @version: 1.0
*/
@Configuration
public class RedisConfig {
static ObjectMapper objectMapper = Mappers.objectMapper.copy();
static ObjectMapper objectMapper2 = Mappers.objectMapper.copy();
static ObjectMapper objectMapper3 = Mappers.objectMapper.copy();
@Bean(value = "redisTemplate")
@Primary
public RedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//redis云服务不能使用keys命令,使用redis缓存可能会有问题
// /**
// 自定义RedisCacheManager,用于在使用@Cacheable时设置ttl
// */
// @Bean
// public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
// RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisTemplate.getConnectionFactory());
// RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));
// return new TtlRedisCacheManager(redisCacheWriter, redisCacheConfiguration);
// }
@Bean("jsonRedisCacheManager")
@Primary
public CacheManager customCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 解决查询缓存转换异常的问题
objectMapper2.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
JavaTimeModule javaTimeModule = new JavaTimeModule();
objectMapper2.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);
//系列化时间格式化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//反序列化时间格式化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//注册进入jackson
objectMapper2.registerModule(javaTimeModule);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper2,
Object.class);
// Configure Redis cache with Jackson serializer
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// jackson
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
// 默认10分钟过期时间
.entryTtl(Duration.ofMinutes(10L))
// 空值不做缓存
.disableCachingNullValues();
// redis 服务端考虑性能问题会禁用keys命令全量操作, 改用scan批量操作
RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory, BatchStrategies.scan(1000));
// Create a cache manager
return new TtlRedisCacheManager(redisCacheWriter,cacheConfiguration);
}
@Bean("listRedisCacheManager")
public CacheManager customListCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 解决查询缓存转换异常的问题
objectMapper3.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
JavaTimeModule javaTimeModule = new JavaTimeModule();
//系列化时间格式化
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//反序列化时间格式化
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
//注册进入jackson
objectMapper3.registerModule(javaTimeModule);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(objectMapper3,
Object.class);
// Configure Redis cache with Jackson serializer
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
// jackson
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
// 默认10分钟过期时间
.entryTtl(Duration.ofMinutes(10L))
// 空值不做缓存
.disableCachingNullValues();
// redis 服务端考虑性能问题会禁用keys命令全量操作, 改用scan批量操作
RedisCacheWriter redisCacheWriter = RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory, BatchStrategies.scan(1000));
// Create a cache manager
return new TtlRedisCacheManager(redisCacheWriter,cacheConfiguration);
}
@Bean
@Primary
public KeyGenerator defaultKeyGenerator(){
return new JsonCacheKeyGenerator(objectMapper);
}
}
二、Spring Cache与Redis的深度集成
1. 声明式缓存原理
Spring Cache通过@Cacheable
和@CacheEvict
注解简化缓存逻辑:
@Cacheable
:方法执行前检查缓存,命中则直接返回结果;未命中则执行方法并将结果存入缓存。
@CacheEvict
:方法执行后清除指定缓存条目,确保数据更新后缓存同步。
1.1声明式缓存详情介绍
请参考另一篇文章:@Cacheable 和 @CacheEvict 注解的详细使用说明及参数解析,结合 Spring Cache 的核心功能和实际开发场景-CSDN博客
2. 缓存键生成策略
默认使用JsonCacheKeyGenerator
生成基于JSON内容的唯一键,例如:
CACHE_DATASOURCE::DB_001
通过ObjectMapper
的序列化配置,确保不同参数生成不同的键,避免缓存冲突。
3. RedisTemplate配置
@Bean(value = "redisTemplate")
@Primary
public RedisTemplate stringRedisTemplate(...) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 使用Jackson序列化值
}
作用 :
键序列化 :使用StringRedisSerializer
保证键的可读性。
值序列化 :通过Jackson
处理复杂对象,支持反序列化时还原类型信息。
三、缓存穿透问题及解决方案
1. 问题分析
当查询不存在的数据时(如databaseCode="invalid"
),如果未缓存空值,每次请求都会穿透到数据库,导致性能问题。
2. 当前配置的局限性
.disableCachingNullValues() // 禁止缓存空值
此配置虽然节省了存储空间,但无法防止缓存穿透。
3. 解决方案对比
方案
实现方式
优点
缺点
缓存空值
.enableCachingNullValues()
简单有效,拦截后续请求
占用缓存空间
布隆过滤器
额外引入布隆过滤器组件
内存占用小,拦截非法请求
需维护额外组件
限流+短TTL
缓存空值并设置短TTL(如30秒)
平衡存储与性能
需配合限流策略
4. 推荐实践
在缓存配置中启用空值缓存并设置短TTL:
.enableCachingNullValues()
.entryTtl(Duration.ofSeconds(30))
同时,在服务层添加限流逻辑:
@Cacheable(CacheConstant.CACHE_DATASOURCE)
public ModuleDatabaseInfo getDataSourceById(String databaseCode) {
if (!rateLimiter.tryAcquire()) {
throw new RateLimitException("请求过于频繁");
}
// 查询数据库逻辑
}
四、自定义缓存管理器的优势
1. TtlRedisCacheManager
通过继承RedisCacheManager
,实现自定义TTL控制:
new TtlRedisCacheManager(redisCacheWriter, config)
动态TTL :支持为不同缓存名称设置独立过期时间。
线程安全 :使用lockingRedisCacheWriter
保证并发写入安全。
2. 多缓存管理器场景
项目中定义了多个CacheManager
(如jsonRedisCacheManager
和listRedisCacheManager
),分别处理普通对象和列表结构,提升灵活性:
@Bean("listRedisCacheManager")
public CacheManager customListCacheManager(...) {
return new TtlRedisCacheManager(redisCacheWriter, config);
}
五、总结与最佳实践
1. 核心优势
高性能 :通过Redis和Spring Cache的集成,减少数据库压力。
可维护性 :声明式注解与集中式配置分离业务逻辑与缓存管理。
扩展性 :自定义缓存管理器支持灵活的TTL和序列化策略。
2. 注意事项
避免缓存雪崩 :为缓存项设置随机过期时间。
监控与告警 :通过Redis的INFO
命令监控缓存命中率。
测试验证 :使用@SpringBootTest
模拟缓存命中与失效场景。
3. 适用场景
高频读取场景 :如数据库连接池配置、用户权限信息。
分布式系统 :通过Redis共享缓存,保证多节点一致性。
通过以上设计,项目实现了高效、灵活的缓存管理,为动态数据源的访问提供了稳定支撑。实际开发中,可根据业务需求调整序列化策略、TTL和缓存穿透防护方案,进一步优化性能与可靠性。