目录

Spring缓存管理器多级缓存的实现

大概今年9月份,在对接抖音抖店 api 时,写的一个多级缓存的实现,本地缓存基于guava。如果需要使用,直接将这些类copy过去就可以。或者这些类抽取到单独的模块,自定义一个 autoconfigurer、start包,就可以无缝整合到 springboot,导包即可自动配置。

基于 RedisCacheManager、RedisCache 等核心类继承扩展。

如果要做的更完善,也可以将本地缓存的一些配置信息,抽取到配置文件中。

实现中的核心在 LayeringCache、ChannelTopicEnum、RedisTopicListener 这三个类。

1 缓存核心类 LayeringCache

继承 RedisCache,自定义我们的缓存控制。

代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
@Slf4j
public class LayeringCache extends RedisCache {
	private final Cache<Object, Object> localCache = CacheBuilder.newBuilder()
			.initialCapacity(1000)
			.maximumSize(20000)
			.expireAfterWrite(120, TimeUnit.SECONDS)
			.build();

	protected LayeringCache(String name, LayingRedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
		super(name, cacheWriter, cacheConfig);
	}

	@Override
	public LayingRedisCacheWriter getNativeCache() {
		return (LayingRedisCacheWriter) super.getNativeCache();
	}


	@Override
	protected Object lookup(Object key) {
		// 这里是获取缓存时,优先从本地缓存获取。本地缓存和redis缓存时伪实时同步的。
		String completeKey = createCacheKey(key);
		Object val = localCache.getIfPresent(completeKey);
		if (val == null && (val = super.lookup(key)) != null) {
			// 如果本地缓存获取不到,则发布REDIS_CACHE_PUT_TOPIC事件,事件内容是 val,即这个缓存值。
			// 在事件处理函数中,会将这个值同步到本地缓存中。
			this.publish(key, val, ChannelTopicEnum.REDIS_CACHE_PUT_TOPIC);
		} else if (log.isDebugEnabled()) {
			log.debug("从应用内缓存获取数据:name:[{}] key:[{}]", getName(), completeKey);
		}
		return val;
	}


	@Override
	public void put(Object key, Object value) {
		super.put(key, value);
		// 发布 REDIS_CACHE_PUT_TOPIC 事件,表示新增一个缓存内容。
		this.publish(key, value, ChannelTopicEnum.REDIS_CACHE_PUT_TOPIC);
	}

	@Override
	public ValueWrapper putIfAbsent(Object key, Object value) {
		ValueWrapper valueWrapper = super.putIfAbsent(key, value);
		// 发布 REDIS_CACHE_PUT_TOPIC 事件,表示新增一个缓存内容。
		this.publish(key, value, ChannelTopicEnum.REDIS_CACHE_PUT_TOPIC);
		return valueWrapper;
	}

	@Override
	public void evict(Object key) {
		super.evict(key);
		// 发布 REDIS_CACHE_CLEAR_TOPIC 事件,表示移除了一个缓存内容。
		this.publish(key, null, ChannelTopicEnum.REDIS_CACHE_CLEAR_TOPIC);
	}

	protected void publish(Object key, Object value, ChannelTopicEnum topic) {
		TopicMsgVo<String, ?, ?> msg = new TopicMsgVo<>(getName(), key, value);
		this.getNativeCache().getConnectionFactory().getConnection().publish(
				topic.getChannel().getBytes(), TopicMsgVo.serializeValue(msg));
	}

	/**
	 * 处理新增消息
	 * @param msg 消息封装
	 */
	protected void processPutMsg(TopicMsgVo<String, ?, ?> msg) {
		String completeKey = createCacheKey(msg.getKey());
		if (msg.getValue() == null) {
			localCache.invalidate(completeKey);
		} else {
			localCache.put(completeKey, msg.getValue());
		}
	}

	/**
	 * 处理清除缓存消息
	 * @param msg 消息封装
	 */
	protected void processClearMsg(TopicMsgVo<String, ?, ?> msg) {
		Object key = createCacheKey(msg.getKey());
		if (key != null) {
			localCache.invalidate(key);
		} else {
			localCache.invalidateAll();
		}
	}

	/**
	 * 创建缓存数据的key
	 * @param key key
	 * @return 生成的缓存key
	 */
	@Override
	protected String createCacheKey(Object key) {
		String cacheKey = super.createCacheKey(key);
		if (!getCacheConfiguration().usePrefix()) {
			return cacheKey;
		}
		String name = this.getName();
		// spring的cacheKey默认分隔符是 :: ,这里改写成 :,即将两个冒号改成一个冒号分隔。
		if (cacheKey.length() == name.length() + key.toString().length() + 2) {
			cacheKey = cacheKey.substring(0, name.length()) + cacheKey.substring(name.length() + 1);
		}
		return cacheKey;
	}

	/**
	 * 处理过期消息
	 * @param stringTopicMsgVo
	 */
	protected void processExpireMsg(TopicMsgVo<String, ?, ?> stringTopicMsgVo) {
		byte[] keyBytes = (byte[]) stringTopicMsgVo.getKey();
		localCache.invalidate(keyBytes);
	}
}

2 LayingRedisCacheWriter

拷贝一个 DefaultRedisCacheWriter 源码,更名为 LayingRedisCacheWriter。

在LayeringCacheManager和 LayeringCache 中需要用到,由于包权限问题,不能直接访问 DefaultRedisCacheWriter,所以需要定义一个。

3 信道-处理函数映射 ChannelTopicEnum

用于存储 LayeringCache 中的一些事件与处理函数的映射关系。比如我们从缓存删除一条数据,那么负载的所有机器都应该将自己的本地缓存的该值移除,以保持数据一致。

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
 * 信道枚举
 * 这里定义了redis缓存中不同的操作事件与处理方法的映射关系。
 * @author Eddie
 */
@Getter
public enum ChannelTopicEnum {
	/**
	 * redis缓存内容变动通知
	 */
	REDIS_CACHE_PUT_TOPIC("tiktok:cache:redis:put", LayeringCache::processPutMsg),
	REDIS_CACHE_CLEAR_TOPIC("tiktok:cache:redis:clear", LayeringCache::processClearMsg),
	REDIS_CACHE_CLEAR_EXPIRE("tiktok:cache:redis:expire", LayeringCache::processExpireMsg),
	;

	ChannelTopicEnum(String channel, BiConsumer<LayeringCache, TopicMsgVo<String, ?, ?>> processNoticeFun) {
		this.channel = channel;
		this.processNoticeFun = processNoticeFun;
	}

	/**
	 * 消息发布信道
	 */
	private final String channel;
	/**
	 * 缓存数据变动后,本地缓存处理方法
	 */
	private final BiConsumer<LayeringCache, TopicMsgVo<String, ?, ?>> processNoticeFun;

	private static final Map<String, ChannelTopicEnum> TOPIC_MAP = new HashMap<>();

	static {
		for (ChannelTopicEnum value : ChannelTopicEnum.values()) {
			TOPIC_MAP.put(value.getChannel(), value);
		}
	}

	public static ChannelTopicEnum getByChannel(String channel) {
		return TOPIC_MAP.get(channel);
	}
}

4 消息监听 RedisTopicListener

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * redis监听器,用来监听自定义的一些channel、包括新增、删除、过期事件。
 * @author Eddie
 */
@ConditionalOnProperty(prefix = "tiktok", name = "use-layering", havingValue = "true")
@Slf4j
@Configuration
public class RedisTopicListener {

	/**
	 * 消息适配器
	 * @param cacheManager 缓存管理器
	 * @return 消息适配器实例
	 */
	@Bean
	public MessageListenerAdapter getMessageListenerAdapter(LayeringCacheManager cacheManager) {
		return new MessageListenerAdapter((MessageListener) (message, pattern) -> {
			try {
				// 监听的队列信息
				String channel = new String(message.getChannel(), StandardCharsets.UTF_8.name());
				// 发送的内容信息
				TopicMsgVo<String, ?, ?> msgVo = TopicMsgVo.deserializeValue(message.getBody());
				// 通过channel获取到消息处理方法
				BiConsumer<LayeringCache, TopicMsgVo<String, ?, ?>> fun = ChannelTopicEnum.getByChannel(channel).getProcessNoticeFun();
				if (log.isDebugEnabled()) {
					log.debug("redis消息信道:[{}],消息内容:[{}]", channel, msgVo);
				}
				// 获取多级缓存对象 LayeringCache
				LayeringCache originCache = cacheManager.getOriginCache(Objects.requireNonNull(msgVo).getName());
				if (originCache != null) {
					// 如果缓存是存在的,则调用回调函数,进行事件的处理。
					fun.accept(originCache, msgVo);
				}
			} catch (UnsupportedEncodingException e) {
				log.warn(e.getMessage(), e);
			}
		});
	}

	/**
	 * 消息监听容器
	 * @param template RedisTemplate
	 * @param messageListenerAdapter 消息适配器
	 * @return 消息监听容器
	 */
	@Bean
	public RedisMessageListenerContainer getRedisMessageListenerContainer(
			RedisTemplate<String, Object> template, MessageListenerAdapter messageListenerAdapter) {
		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
		container.setConnectionFactory(Objects.requireNonNull(template.getConnectionFactory()));
		ChannelTopicEnum[] values = ChannelTopicEnum.values();
		for (ChannelTopicEnum value : values) {
			// 将所有定义的channel常量,添加到监听。
			container.addMessageListener(messageListenerAdapter, new ChannelTopic(value.getChannel()));
		}
		return container;
	}

	/**
	 * key过期监听
	 */
	@Bean
	public KeyExpirationEventMessageListener getKeyExpirationEventMessageListener(
			RedisMessageListenerContainer container, LayeringCacheManager cacheManager) {
		return new LayeringKeyExpirationEventMessageListener(container, cacheManager);
	}

	static class LayeringKeyExpirationEventMessageListener extends KeyExpirationEventMessageListener {
		private LayeringCacheManager cacheManager;
		
		public LayeringKeyExpirationEventMessageListener(RedisMessageListenerContainer listenerContainer, LayeringCacheManager cacheManager) {
			super(listenerContainer);
			this.cacheManager = cacheManager;
		}

		@Override
		protected void doHandleMessage(Message message) {
			if (log.isDebugEnabled()) {
				log.debug("redisKey:{}过期,从本地缓存中移除", new String(message.getBody()));
			}
			// 当监听到过期事件后,获取到回调函数,通过回调,移除本地缓存。
			BiConsumer<LayeringCache, TopicMsgVo<String, ?, ?>> fun = ChannelTopicEnum.REDIS_CACHE_CLEAR_EXPIRE.getProcessNoticeFun();
			TopicMsgVo<String, ?, ?> msgVo = new TopicMsgVo<>(null, message.getBody(), null);
			Collection<LayeringCache> caches = cacheManager.getOriginCaches();
			for (LayeringCache cache : caches) {
				fun.accept(cache, msgVo);
			}
		}
	}
}

5 消息封装 TopicMsgVo

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
 * 消息封装及消息的序列化
 * @author Eddie
 */
@Slf4j
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TopicMsgVo<N, K, V> implements Serializable {
	/** 名称 */
	private N name;
	/** 缓存key */
	private K key;
	/** 值 */
	private V value;

	/**
	 * 序列化
	 * @param msg 消息封装
	 * @return 转换成字节数组
	 */
	public static byte[] serializeValue(TopicMsgVo<String, ?, ?> msg) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(msg);
		} catch (Exception e) {
			log.warn(e.getMessage(), e);
		}
		return bos.toByteArray();
	}

	/**
	 * 反序列化
	 * @param bytes 字节数组
	 * @return 转换成消息封装对象
	 */
	@SuppressWarnings("unchecked")
	public static TopicMsgVo<String, ?, ?> deserializeValue(byte[] bytes) {
		ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
		try {
			return (TopicMsgVo<String, ?, ?>) new ObjectInputStream(bis).readObject();
		} catch (Exception e) {
			log.warn(e.getMessage(), e);
		}
		return null;
	}
}

6 缓存管理器 LayeringCacheManager

继承 RedisCacheManager,重写 createRedisCache()。

定义 LayeringCacheManagerBuilder,从 RedisCacheManager 建造类中拷贝,将里面构建的 RedisCache,替换为 LayeringCache。

代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public class LayeringCacheManager extends RedisCacheManager {
	/** 用来存储 cacheName - LayeringCache映射关系 */
	private final ConcurrentHashMap<String, LayeringCache> originLayeringCacheMap = new ConcurrentHashMap<>(16);

	public LayeringCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
		super(cacheWriter, defaultCacheConfiguration);
	}

	public LayeringCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
		super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
	}

	public LayeringCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
		super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
	}

	public LayeringCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
		super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
	}

	public LayeringCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
		super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
	}

	/**
	 * 创建 RedisCache,重写方法的关键在这
	 */
	@SneakyThrows
	@Override
	protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
		Class<? extends LayeringCacheManager> clazz = this.getClass();
		Field cacheWriter = ReflectionUtils.findField(clazz, "cacheWriter");
		cacheWriter.setAccessible(true);
		LayingRedisCacheWriter cacheWriterVal = (LayingRedisCacheWriter) cacheWriter.get(this);
		Field defaultCacheConfig = ReflectionUtils.findField(clazz, "defaultCacheConfig");
		defaultCacheConfig.setAccessible(true);
		RedisCacheConfiguration defaultCacheConfigVal = (RedisCacheConfiguration) defaultCacheConfig.get(this);
		// 使用自定义的 LayeringCache 替换掉原有的 RedisCache
		LayeringCache cache = new LayeringCache(name, cacheWriterVal, cacheConfig != null ? cacheConfig : defaultCacheConfigVal);
		originLayeringCacheMap.put(name, cache);
		return cache;
	}

	@Override
	protected RedisCache getMissingCache(String name) {
		return super.getMissingCache(name);
	}

	@Override
	public Cache getCache(String name) {
		return super.getCache(name);
	}

	public LayeringCache getOriginCache(String name) {
		return originLayeringCacheMap.get(name);
	}

	public Collection<LayeringCache> getOriginCaches() {
		return originLayeringCacheMap.values();
	}

	public static class LayeringCacheManagerBuilder {

		private @Nullable
		RedisCacheWriter cacheWriter;
		private RedisCacheConfiguration defaultCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
		private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
		private boolean enableTransactions;
		boolean allowInFlightCacheCreation = true;

		private LayeringCacheManagerBuilder() {
		}

		private LayeringCacheManagerBuilder(RedisCacheWriter cacheWriter) {
			this.cacheWriter = cacheWriter;
		}
		
		public static LayeringCacheManagerBuilder fromConnectionFactory(RedisConnectionFactory connectionFactory) {
			Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
			// 这里是个关键点,将RedisCache对象替换为自定义的 LayingRedisCacheWriter
			return new LayeringCacheManagerBuilder(new LayingRedisCacheWriter(connectionFactory));
		}

		public static LayeringCacheManagerBuilder fromCacheWriter(LayingRedisCacheWriter cacheWriter) {
			Assert.notNull(cacheWriter, "CacheWriter must not be null!");
			return new LayeringCacheManagerBuilder(cacheWriter);
		}

		public LayeringCacheManagerBuilder cacheDefaults(RedisCacheConfiguration defaultCacheConfiguration) {
			Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");
			this.defaultCacheConfiguration = defaultCacheConfiguration;
			return this;
		}

		public LayeringCacheManagerBuilder cacheWriter(RedisCacheWriter cacheWriter) {
			Assert.notNull(cacheWriter, "CacheWriter must not be null!");
			this.cacheWriter = cacheWriter;
			return this;
		}

		public LayeringCacheManagerBuilder transactionAware() {
			this.enableTransactions = true;
			return this;
		}
		
		public LayeringCacheManagerBuilder initialCacheNames(Set<String> cacheNames) {
			Assert.notNull(cacheNames, "CacheNames must not be null!");
			cacheNames.forEach(it -> withCacheConfiguration(it, defaultCacheConfiguration));
			return this;
		}

		public LayeringCacheManagerBuilder withInitialCacheConfigurations(
				Map<String, RedisCacheConfiguration> cacheConfigurations) {

			Assert.notNull(cacheConfigurations, "CacheConfigurations must not be null!");
			cacheConfigurations.forEach((cacheName, configuration) -> Assert.notNull(configuration,
					String.format("RedisCacheConfiguration for cache %s must not be null!", cacheName)));

			this.initialCaches.putAll(cacheConfigurations);
			return this;
		}

		public LayeringCacheManagerBuilder withCacheConfiguration(String cacheName,
		                                                          RedisCacheConfiguration cacheConfiguration) {
			Assert.notNull(cacheName, "CacheName must not be null!");
			Assert.notNull(cacheConfiguration, "CacheConfiguration must not be null!");
			this.initialCaches.put(cacheName, cacheConfiguration);
			return this;
		}
		
		public LayeringCacheManagerBuilder disableCreateOnMissingCache() {
			this.allowInFlightCacheCreation = false;
			return this;
		}
		
		public Set<String> getConfiguredCaches() {
			return Collections.unmodifiableSet(this.initialCaches.keySet());
		}

		public Optional<RedisCacheConfiguration> getCacheConfigurationFor(String cacheName) {
			return Optional.ofNullable(this.initialCaches.get(cacheName));
		}
		
		public LayeringCacheManager build() {
			Assert.state(cacheWriter != null, "CacheWriter must not be null! You can provide one via 'RedisCacheManagerBuilder#cacheWriter(RedisCacheWriter)'.");
			LayeringCacheManager cm = new LayeringCacheManager(cacheWriter, defaultCacheConfiguration, initialCaches,
					allowInFlightCacheCreation);
			cm.setTransactionAware(enableTransactions);
			return cm;
		}
	}
}

7 使用 - 配置缓存管理器

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@Configuration
@EnableCaching
public class TiktokRedisConfig {
	@Bean(name = "redisTemplate")
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
		// 创建RedisTemplate<String, Object>对象
		RedisTemplate<String, Object> template = new RedisTemplate<>();
		// 配置连接工厂
		template.setConnectionFactory(factory);
		// 定义Jackson2JsonRedisSerializer序列化对象
		Jackson2JsonRedisSerializer<Object> jacksonSeial = new Jackson2JsonRedisSerializer<>(Object.class);
		ObjectMapper om = new ObjectMapper();
		// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会报异常
		om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
		jacksonSeial.setObjectMapper(om);
		StringRedisSerializer stringSerial = new StringRedisSerializer();
		// redis key 序列化方式使用stringSerial
		template.setKeySerializer(stringSerial);
		// redis value 序列化方式使用jackson
		template.setValueSerializer(jacksonSeial);
		// redis hash key 序列化方式使用stringSerial
		template.setHashKeySerializer(stringSerial);
		// redis hash value 序列化方式使用jackson
		template.setHashValueSerializer(jacksonSeial);
		template.afterPropertiesSet();
		return template;
	}

	@ConditionalOnProperty(prefix = "tiktok", name = "use-layering", havingValue = "false", matchIfMissing = true)
	@Bean(name = "cacheManager")
	public CacheManager cacheManager(RedisTemplate<String, Object> template) {
		RedisCacheConfiguration defaultCacheConfiguration = getRedisCacheConfiguration(template);
		return RedisCacheManagerBuilder
				// Redis 连接工厂
				.fromConnectionFactory(Objects.requireNonNull(template.getConnectionFactory()))
				// 缓存配置
				.cacheDefaults(defaultCacheConfiguration)
				// 配置同步修改或删除 put/evict
				.transactionAware()
				.build();
	}

	@ConditionalOnProperty(prefix = "tiktok", name = "use-layering", havingValue = "true")
	@Bean(name = "layeringCacheManager")
	public LayeringCacheManager layeringCacheManager(RedisTemplate<String, Object> template) {
		RedisCacheConfiguration defaultCacheConfiguration = getRedisCacheConfiguration(template);
		return LayeringCacheManager.LayeringCacheManagerBuilder
				// Redis 连接工厂
				.fromConnectionFactory(Objects.requireNonNull(template.getConnectionFactory()))
				// 缓存配置
				.cacheDefaults(defaultCacheConfiguration)
				// 配置同步修改或删除 put/evict
				.transactionAware()
				.build();
	}

	private RedisCacheConfiguration getRedisCacheConfiguration(RedisTemplate<String, Object> template) {
		return RedisCacheConfiguration
				.defaultCacheConfig()
				// 设置key为String
				.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getStringSerializer()))
				// 设置value 为自动转Json的Object
				.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(template.getValueSerializer()))
				// 不缓存null
				.disableCachingNullValues()
				// 缓存数据保存1小时
//						.entryTtl(Duration.ofHours(1));
				.entryTtl(Duration.ofSeconds(GlobalConstants.REDIS_DEFAULT_EXPIRE_SECOND));
	}
}

8 使用 - 案例

使用上与spring缓存管理器无任何差别,使用 @Cacheable 等注解即可使缓存生效。

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Slf4j
@Service
public class UserServiceImpl implements UserService {

	@Cacheable(cacheNames = "tiktok:log:operation-log", key = "#xxx.field1+'-'+#xxx.field2")
	@Override
	public XXX getXXX(XXX xxx) {
		// 业务代码省略...
        return XXX;
	}
}