【NoSql】缓存管理器CacheManager使用
缓存管理器CacheManager
一、背景
代码并发量因建行活动页上升,大量请求打到Mongo导致数据库cpu100%从而服务不可用,目前解决方案,使用编程式缓存,即对缓存的操作与业务代码耦合。目前基本上可以解决并发问题。此次提出CacheManager主要是优化代码。使用声明式,即注解的方式,灵活操纵缓存,不需要与业务代码耦合。
二、与Springboot2集成
1、引入依赖
<!--SpringCache的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、SpringCache配置
/**
* @Title: CacheManagerConfiguration
* @author: simon
* @date: 2021/12/27 14:14
*/
@EnableCaching
@Configuration
public class CacheManagerConfiguration extends CachingConfigurerSupport {
private static String SEPARATOR = "&";
@Autowired
ReactiveMongoTemplate reactiveMongoTemplate;
/**
* 缓存管理器
*
* @param lettuceConnectionFactory
* @return
* @author simon
*/
@Bean("redisCacheManager")
@Primary
public CacheManager cacheManager(@Qualifier("RedisConnectionFactory")
LettuceConnectionFactory lettuceConnectionFactory)
{
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
// 解决查询缓存转换异常的问题
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
//反序列化时候遇到不匹配的属性并不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//序列化时候遇到空对象不抛出异常
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//反序列化的时候如果是无效子类型,不抛出异常
mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false);
//不使用默认的dateTime进行序列化,
mapper.configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false);
//使用JSR310提供的序列化类,里面包含了大量的JDK8时间序列化类
mapper.registerModule(new JavaTimeModule());
//启用反序列化所需的类型信息,在属性中添加@class
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer(mapper);
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
// 初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(lettuceConnectionFactory);
// 默认缓存配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.computePrefixWith(comPrefix -> RedisConstants.KEY_BASE_PRIMITIVE.concat(RedisConstants.KEY_SEPARATOR)
.concat("cacheManager")
.concat(RedisConstants.KEY_SEPARATOR)
.concat(comPrefix)
.concat(RedisConstants.KEY_SEPARATOR))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(pair);
// 初始化缓存key
Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(16);
configurationMap.put(CacheDurationConstant.FOREVER_CACHE, defaultCacheConfig);
configurationMap.put(CacheDurationConstant.THREE_DAYS_CACHE, defaultCacheConfig.entryTtl(Duration.ofDays(3)));
configurationMap.put(CacheDurationConstant.ONE_DAYS_CACHE, defaultCacheConfig.entryTtl(Duration.ofDays(1)));
configurationMap.put(CacheDurationConstant.ONE_HOURS_CACHE, defaultCacheConfig.entryTtl(Duration.ofHours(1)));
configurationMap.put(CacheDurationConstant.TWO_MINUTES_CACHE, defaultCacheConfig.entryTtl(Duration.ofMinutes(2)));
configurationMap.put(CacheDurationConstant.FIVE_SECONDS_CACHE, defaultCacheConfig.entryTtl(Duration.ofSeconds(5)));
return RedisCacheManager.builder(redisCacheWriter)
// 初始化缓存空间
.initialCacheNames(configurationMap.keySet())
// 初始化缓存配置
.withInitialCacheConfigurations(configurationMap)
// 默认缓存配置
.cacheDefaults(defaultCacheConfig)
.build();
}
@Bean
@Primary
@Override //继承上面这个类,并且加上这个之后才能把它设置为默认的。
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
String className = target.getClass().getSimpleName();
String methodName = method.getName();
StringBuilder paramBuilder = new StringBuilder();
if (params.length > 0) {
Object param = params[0];
// 参数为map自定义key=类名+方法名+map的key-value值
if (param instanceof Map) {
Map<String, Object> map = (Map<String, Object>) param;
if (!map.isEmpty()) {
for (String key : map.keySet()) {
paramBuilder.append(key).append("-").append(map.get(key)).append(CacheManagerConfiguration.SEPARATOR);
}
}
} else {
for (Object key : params) {
if (key != null) {
paramBuilder.append(JSONObject.toJSONString(key)).append(CacheManagerConfiguration.SEPARATOR);
}
}
}
}
String paramString = paramBuilder.toString().replaceAll(":", ":");
if (paramString.endsWith(CacheManagerConfiguration.SEPARATOR)) {
paramString = paramString.substring(0, paramString.length() - 1);
}
SimpleKey key = new SimpleKey(className, methodName, paramString);
return key;
};
}
}
详情请看附件
注:注意不要将ObjectMapper加入到Spring容器中,因为Spring容器中存在一个ObjectMapper,以用于@RequestBody
、ResponseBody
、RestTemplate
等地的序列化和反序列化。
为什么不采用Spring容器的ObjectMapper对象,而要自己设置是因为Redis配置了objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
属性,在序列化时记录类/属性的类型,以便在反序列化时得到POJO对象。
三、使用
1、基于声明式注释的缓存
SpringCache是Service层的声明式缓存。即无需与业务代码耦合,通过注解完成缓存。
注解通用属性:
属性/方法名 | 解释 |
---|---|
value | 缓存名,必填,它指定了你的缓存存放在哪块命名空间 |
cacheNames | 与 value 差不多,二选一即可 |
key | 可选属性,可以使用 SpEL 标签自定义缓存的key |
keyGenerator | key的生成器。key/keyGenerator二选一使用 |
cacheManager | 指定缓存管理器 |
cacheResolver | 指定获取解析器 |
condition | 条件符合则缓存 |
unless | 条件符合则不缓存 |
1.1、@Cacheable注解
根据方法对其返回结果进行缓存,下次请求时,如果缓存存在,则直接读取缓存数据返回;如果缓存不存在,则执行方法,并把返回的结果存入缓存中。一般用在查询方法上。 查看源码,特殊属性值如下:
属性/方法名 | 解释 |
---|---|
sync | 是否使用异步模式,默认为false |
附:SpringCache的元数据,可用在Key上
名称 | 位置 | 描述 | 例子 |
---|---|---|---|
methodMame | root | 被调用方法的名称 | #root.methodName |
method | root | 被调用的方法 | #root.method.name |
target | root | 被调用的目标对象 | #root.target |
targetClass | root | 被调用目标的类 | #root.targetClass |
args | root | 用于被调用目标的参数值(数组) | #root.args[0] |
caches | root | 执行当前方法缓存的集合 | #root.caches[0].name |
参数名称 | 调用的方法 | 方法的任何参数名称 | #iban或#a0 |
result | 调用的方法 | 仅用在unless,方法调用的结果(缓存值) | #result |
1.2、@CachePut注解
使用该注解标志的方法,每次都会执行,并将结果存入指定的缓存中。其他方法可以直接从响应的缓存中读取缓存数据,而不需要再去查询数据库。一般用在新增方法上。
1.3、@CacheEvict注解
使用该注解标志的方法,会清空指定的缓存。一般用在更新或者删除方法上 查看源码,特殊属性值如下:
属性/方法名 | 解释 |
---|---|
allEntries | 该参数指示是否需要在整个缓存范围内逐出而不仅仅是基于Key的逐条逐出,默认为 false。如果指定为 true,则方法调用后将立即清空所有的缓存 |
beforeInvocation | 是否在方法执行前就清空,默认为 false。如果指定为 true,则在方法执行前就会清空缓存 |
1.4、@Caching注解
该注解可以实现同一个方法上同时使用多种注解。