【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,以用于@RequestBodyResponseBodyRestTemplate等地的序列化和反序列化。

为什么不采用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注解

该注解可以实现同一个方法上同时使用多种注解。

热门相关:超武穿梭   网游之逆天飞扬   第一神算:纨绔大小姐   薄先生,情不由己   刺客之王