暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

在spring-boot的spring-cache中的扩展redis缓存的ttl和key名

大萌小路 2019-02-18
499

前提

spring-cache大家都用过,其中使用redis-cache大家也用过,至于如何使用怎么配置,本篇就不重点描述了。本篇主要解决2个问题,第一个问题使用redis做缓存时对每个key进行自定义的过期时间配置,第二个使用redis做缓存时 @Cacheable(value="value",key="#p0")
 ,最后生成的key会在value和p0中间的有(::)2个冒号,与redis的key名一个冒号间隔的风格不符。

本篇以spring-boot 2.1.2和 spirng 5.1.4为基础来讲解。RedisCacheManage在spring-data-redis 2.x中相对于1.x的变动很大,本篇即在2.x的版本中实现。

redis cache的过期时间

我们都知道redis的过期时间,是用它做缓存或者做业务操作的灵性。在使用 @Cacheable(value="value",key="#p0")
注解时即可。具体的使用方法参考网上。

RedisCacheManager

我们先来看看RedisCacheManager,RedisCacheWriter接口是对redis操作进行包装的一层低级的操作。defaultCacheConfig是redis的默认配置,在下一个选项卡中详细介绍。initialCacheConfiguration是对各个单独的缓存进行各自详细的配置(过期时间就是在此配置的),allowInFlightCacheCreation是否允许创建不事先定义的缓存,如果不存在即使用默认配置。RedisCacheManagerBuilder使用桥模式,我们可以用它构建RedisCacheManager。

  1. public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {


  2.   private final RedisCacheWriter cacheWriter;

  3.   private final RedisCacheConfiguration defaultCacheConfig;

  4.   private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;

  5.   private final boolean allowInFlightCacheCreation;

  6.   public static class RedisCacheManagerBuilder {}


  7. }

AbstractTransactionSupportingCacheManager

AbstractTransactionSupportingCacheManager加入事务概念,将操作与事务绑定,包装了一层事务。

  1. public abstract class AbstractTransactionSupportingCacheManager extends AbstractCacheManager {


  2.   private boolean transactionAware = false;


  3.   public void setTransactionAware(boolean transactionAware) {

  4.      this.transactionAware = transactionAware;

  5.   }


  6.   public boolean isTransactionAware() {

  7.      return this.transactionAware;

  8.   }


  9.   @Override

  10.   protected Cache decorateCache(Cache cache) {

  11.      return (isTransactionAware() ? new TransactionAwareCacheDecorator(cache) : cache);

  12.   }


  13. }

RedisCacheConfiguration

ttl是过期时间,cacheNullValues是否允许存null值,keyPrefix缓存前缀规则,usePrefix是否允许使用前缀。keySerializationPair缓存key序列化,valueSerializationPair缓存值序列化此处最好自己使用jackson的序列号替代原生的jdk序列化,conversionService做转换用的。

  1. public class RedisCacheConfiguration {


  2.   private final Duration ttl;

  3.   private final boolean cacheNullValues;

  4.   private final CacheKeyPrefix keyPrefix;

  5.   private final boolean usePrefix;


  6.   private final SerializationPair<String> keySerializationPair;

  7.   private final SerializationPair<Object> valueSerializationPair;


  8.   private final ConversionService conversionService;


  9. }

RedisCacheManager

再来看看如何配置RedisCacheManager

RedisCacheAutoConfiguration

配置前通过 RedisAutoConfiguration
配置可以获取到redis相关配置包括redisTemplate,因为spring-boot2中redis使用Lettuce作为客户端,相关配置在 LettuceConnectionConfiguration
中。 在去加载CacheProperties和CustomCacheProperties配置。 通过RedisCacheManagerBuilder去构造RedisCacheManager,使用非加锁的redis缓存操作,redis默认配置使用的是cacheProperties中的redis,最后根据我们自定义的customCacheProperties阔以针对单个的key设置单独的redis缓存配置。

getDefaultRedisCacheConfiguration主要先通过RedisCacheConfiguration的默认创建方法 defaultCacheConfig
创建默认的配置,在通过getJackson2JsonRedisSerializer创建默认value格式化(使用jackson代替jdk序列化),然后通过redis缓存配置的是spring-cache的CacheProperties去修改配置项。

最后根据配置构建出RedisCacheConfiguration。

  1. @Slf4j

  2. @EnableCaching

  3. @Configuration

  4. @AutoConfigureAfter(RedisAutoConfiguration.class)

  5. @EnableConfigurationProperties({CacheProperties.class, CustomCacheProperties.class})

  6. @ConditionalOnClass({Redis.class, RedisCacheConfiguration.class})

  7. public class RedisCacheAutoConfiguration {


  8.    @Autowired

  9.    private CacheProperties cacheProperties;


  10.    @Bean

  11.    public RedisCacheManager redisCacheManager(CustomCacheProperties customCacheProperties,

  12.                                               RedisConnectionFactory redisConnectionFactory) {

  13.        RedisCacheConfiguration defaultConfiguration = getDefaultRedisCacheConfiguration();

  14.        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.RedisCacheManagerBuilder

  15.                .fromCacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))

  16.                .cacheDefaults(defaultConfiguration);


  17.        Map<String, RedisCacheConfiguration> map = Maps.newHashMap();

  18.        Optional.ofNullable(customCacheProperties)

  19.                .map(p -> p.getCustomCache())

  20.                .ifPresent(customCache -> {

  21.                    customCache.forEach((key, cache) -> {

  22.                        RedisCacheConfiguration cfg = handleRedisCacheConfiguration(cache, defaultConfiguration);

  23.                        map.put(key, cfg);

  24.                    });

  25.                });

  26.        builder.withInitialCacheConfigurations(map);

  27.        return builder.build();

  28.    }


  29.    private RedisCacheConfiguration getDefaultRedisCacheConfiguration() {

  30.        Redis redisProperties = cacheProperties.getRedis();

  31.        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();


  32.        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = getJackson2JsonRedisSerializer();

  33.        config = config.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()));

  34.        config = config.serializeValuesWith(SerializationPair.fromSerializer(jackson2JsonRedisSerializer));

  35.        config = handleRedisCacheConfiguration(redisProperties, config);

  36.        return config;

  37.    }


  38.    private Jackson2JsonRedisSerializer getJackson2JsonRedisSerializer() {

  39.        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

  40.        ObjectMapper om = new ObjectMapper();

  41.        om.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.ANY);

  42.        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

  43.        om.setSerializationInclusion(Include.NON_NULL);

  44.        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

  45.        jackson2JsonRedisSerializer.setObjectMapper(om);

  46.        return jackson2JsonRedisSerializer;

  47.    }


  48.    private RedisCacheConfiguration handleRedisCacheConfiguration(Redis redisProperties,

  49.                                                                  RedisCacheConfiguration config) {

  50.        if (Objects.isNull(redisProperties)) {

  51.            return config;

  52.        }


  53.        if (redisProperties.getTimeToLive() != null) {

  54.            config = config.entryTtl(redisProperties.getTimeToLive());

  55.        }

  56.        if (redisProperties.getKeyPrefix() != null) {

  57.            config = config.computePrefixWith(cacheName -> cacheName + redisProperties.getKeyPrefix());

  58.        }

  59.        if (!redisProperties.isCacheNullValues()) {

  60.            config = config.disableCachingNullValues();

  61.        }

  62.        if (!redisProperties.isUseKeyPrefix()) {

  63.            config = config.disableKeyPrefix();

  64.        }

  65.        return config;

  66.    }


  67. }

CustomCacheProperties

我们自定的缓存的配置,使用了现有的 CacheProperties.Redis
作为配置类。

  1. @Data

  2. @ConfigurationProperties(prefix = "damon.cache")

  3. public class CustomCacheProperties {


  4.    private Map<String, CacheProperties.Redis> customCache;


  5. }

Redis

Redis的key配置,过期时间,是否允许缓存空值默认可以,key的前缀,是否允许使用key前缀

  1. public static class  {


  2.   private Duration timeToLive;


  3.   private boolean cacheNullValues = true;


  4.   private String keyPrefix;


  5.   private boolean useKeyPrefix = true;


  6. }

yml配置

再来看看配置项

spring.cache.redis就为当前redis-cache的默认配置

底下的damon.cache就为自定义配置(默认20秒),如下配置了 testA
和 testB
2个自定义key的过期时间(一个40秒,一个50秒)

  1. spring:

  2.  redis:

  3.    host: localhost

  4.    port: 6379

  5.  cache:

  6.    redis:

  7.      time-to-live: 20s


  8. damon:

  9.  cache:

  10.    custom-cache:

  11.      testA:

  12.        time-to-live: 40s

  13.      testB:

  14.        time-to-live: 50s

redis-cache的key名调整

从上述我们可以看出使用后,缓存过期时间可以自定义配置了,但是key名中间有2个冒号。

RedisCache

RedisCache中的createCacheKey方法是生成redis的key,从中可以看出是否使用prefix,使用的话通过prefixCacheKey方法生成,借用了redisCache配置项来生成。

  1. private final RedisCacheConfiguration cacheConfig;


  2. protected String createCacheKey(Object key) {


  3.   String convertedKey = convertKey(key);


  4.   if (!cacheConfig.usePrefix()) {

  5.      return convertedKey;

  6.   }


  7.   return prefixCacheKey(convertedKey);

  8. }


  9. private String prefixCacheKey(String key) {


  10.   // allow contextual cache names by computing the key prefix on every call.

  11.   return cacheConfig.getKeyPrefixFor(name) + key;

  12. }

RedisCacheConfiguration

在redisCache配置项中使用getKeyPrefixFor方法来生成完整的redis的key名,通过 keyPrefix.compute来生成。

  1. private final CacheKeyPrefix keyPrefix;


  2. public String getKeyPrefixFor(String cacheName) {


  3.   Assert.notNull(cacheName, "Cache name must not be null!");


  4.   return keyPrefix.compute(cacheName);

  5. }

CacheKeyPrefix

这里就看到我们使用处,而且看到了默认实现有2个冒号的实现。

其实是在RedisCacheConfiguration中有个默认实现方法,里面用的就是CacheKeyPrefix的默认实现。我们只有覆盖此处即可。

  1. @FunctionalInterface

  2. public interface CacheKeyPrefix {


  3. //计算在redis中的缓存名


  4. String compute(String cacheName);


  5. //默认实现,中间用的就是::


  6. static CacheKeyPrefix simple() {

  7. return name -> name + "::";

  8.   }

  9. }

总结

参考上文,使用 RedisCacheConfiguration
的 computePrefixWith(cacheName->cacheName+redisProperties.getKeyPrefix())
实现key调整。

题外话

我们再来聊聊spring-cache,实际上其实它就是把缓存的使用给抽象了,在对缓存的具体实现的过程中给抽出来。其实最重要的就是 Cache
和 CacheManager
2个接口,简单的实现如 SimpleCacheManager

文章转载自大萌小路,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论