【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注解
该注解可以实现同一个方法上同时使用多种注解。

 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号