SpringBoot 整合 Cache 并改造
本次改造主要实现的功能
- 缓存数据时自定义缓存时间
- 缓存失效时的模糊删除
代码实现
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
属性配置
spring:
cache:
type: redis
CacheConfig 缓存管理
通过 CacheConfig 配置 CacheManager ,它是缓存管理的核心组件之一,它负责管理和操作缓存,这里序列化工具与 RedisTemplate 保持一致,减少不必要的问题。
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
//RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
CustomizedRedisCacheWriter customizedRedisCacheWriter = new CustomizedRedisCacheWriter(connectionFactory, BatchStrategies.keys());
RedisConfigCacheManager redisConfigCacheManager = new RedisConfigCacheManager(customizedRedisCacheWriter, connectionFactory, redisCacheConfiguration);
return redisConfigCacheManager;
}
}
RedisConfigCacheManager 缓存管理
RedisConfigCacheManager 继承原 RedisCacheManager 对原缓存功能进行增强。在这里我们构造了自定义的 CustomizedRedisCache 对象。
public class RedisConfigCacheManager extends RedisCacheManager {
private final RedisConnectionFactory redisConnectionFactory;
private final RedisCacheWriter cacheWriter;
public RedisConfigCacheManager(RedisCacheWriter cacheWriter,RedisConnectionFactory redisConnectionFactory,
RedisCacheConfiguration defaultCacheConfiguration) {
super(cacheWriter, defaultCacheConfiguration);
this.redisConnectionFactory = redisConnectionFactory;
this.cacheWriter = cacheWriter;
}
@Override
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
return new CustomizedRedisCache(name, this.cacheWriter,redisConnectionFactory, cacheConfig);
}
}
CustomizedRedisCache key 的模糊清理
CustomizedRedisCache 继承 RedisCache ,这里重写了缓存失效的方法
public class CustomizedRedisCache extends RedisCache {
private static final String WILD_CARD = "*";
private final String name;
private final RedisCacheWriter cacheWriter;
private final ConversionService conversionService;
private final RedisConnectionFactory redisConnectionFactory;
protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisConnectionFactory connectionFactory, RedisCacheConfiguration cacheConfig) {
super(name, cacheWriter, cacheConfig);
this.name = name;
this.cacheWriter = cacheWriter;
this.conversionService = cacheConfig.getConversionService();
this.redisConnectionFactory = connectionFactory;
}
@Override
public void evict(Object key) {
if (key instanceof String) {
String keyString = key.toString();
//模糊清理,使用 clear 模式
if (keyString.contains(WILD_CARD)) {
byte[] pattern = (byte[])this.conversionService.convert(this.createCacheKey(key), byte[].class);
this.cacheWriter.clean(this.name, pattern);
return;
}
}
super.evict(key);
}
}
CustomizedRedisCacheWriter 缓存写入指定失效时间
自定义 CustomizedRedisCacheWriter, 重写默认的 DefaultRedisCacheWriter ,DefaultRedisCacheWriter是跟 redis 交互的底层实现类,在put 自定义实现指定失效的时间。
public class CustomizedRedisCacheWriter implements RedisCacheWriter {
private RedisConnectionFactory connectionFactory;
private Duration sleepTime;
private CacheStatisticsCollector statistics;
private BatchStrategy batchStrategy;
CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, BatchStrategy batchStrategy) {
this(connectionFactory, Duration.ZERO, batchStrategy);
}
CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime, BatchStrategy batchStrategy) {
this(connectionFactory, sleepTime, CacheStatisticsCollector.none(), batchStrategy);
}
CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime, CacheStatisticsCollector cacheStatisticsCollector, BatchStrategy batchStrategy) {
Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
Assert.notNull(sleepTime, "SleepTime must not be null!");
Assert.notNull(cacheStatisticsCollector, "CacheStatisticsCollector must not be null!");
Assert.notNull(batchStrategy, "BatchStrategy must not be null!");
this.connectionFactory = connectionFactory;
this.sleepTime = sleepTime;
this.statistics = cacheStatisticsCollector;
this.batchStrategy = batchStrategy;
}
/**
* 这里实现了自定义的时间
* @param name
* @param key
* @param value
* @param ttl
*/
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
execute(name, connection -> {
int index = name.lastIndexOf("#");
if (index > 0){
String expireString = name.substring(index + 1);
long expireTime = Long.parseLong(expireString);
connection.set(key, value, Expiration.from(expireTime, TimeUnit.MINUTES), RedisStringCommands.SetOption.upsert());
}else if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
this.statistics.incPuts(name);
}
使用案例
缓存
// #后面是缓存时间,单位是分钟,缓存时间为默认值的话不用写
@Cacheable(value = "price#30")
public BaseResult<VO> getPrice(@RequestBody Req req) {
Resp res = service.query(req);
return BaseResult.success(res);
}
//指定 key,对于确定的key,可以不用拼接 *
@Cacheable(value = "price#30",key = "'*' + #req.code+ '*'")
public BaseResult<VO> getPrice(@RequestBody Req req) {
Resp res = service.query(req);
return BaseResult.success(res);
}
//指定条件,满足条件才缓存
@Cacheable(value = "price#30",condition = "#req.code!=null and #req.code.length() > 0")
public BaseResult<VO> getPrice(@RequestBody Req req) {
Resp res = service.query(req);
return BaseResult.success(res);
}
//全部清空
@CacheEvict(value = "price#30",allEntries = true)
public BaseResult<VO> updatePrice(@RequestBody Req req) {
Resp res = service.update(req);
return BaseResult.success(res);
}
时间处理
LocalDate LocalDateTime 序列化
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate importDatetime;
Redis 集群模式下的范围扫描删除
redis 集群模式下范围扫描并删除的方法
static class Scan implements BatchStrategy {
private final int batchSize;
Scan(int batchSize) {
this.batchSize = batchSize;
}
public long cleanCache(RedisConnection connection, String name, byte[] pattern) {
Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count((long)this.batchSize).match(pattern).build());
long count = 0L;
PartitionIterator<byte[]> partitions = new PartitionIterator(cursor, this.batchSize);
while(partitions.hasNext()) {
List<byte[]> keys = partitions.next();
count += (long)keys.size();
if (keys.size() > 0) {
connection.del((byte[][])keys.toArray(new byte[0][]));
}
}
return count;
}
}
其他
- 重写 toString,做好验证,检查 toString 方法是否正常
- 可以自定义key 的生成策略