两级缓存: 本地缓存使用Caffeine, 远程缓存使用redis. 也可以只使用本地缓存或者只使用redis缓存
基于SpringBoot 2.4.0, 依赖如下:
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.9.0</version>
</dependency>
-
application.yml
配置multi: local: # Caffeine配置参考:com.github.benmanes.caffeine.cache.CaffeineSpec spec: maximumSize=100,expireAfterWrite=90s settings: - name: dict spec: maximumSize=100,expireAfterWrite=60s remote: spec: ttl=90 settings: - name: dict
-
实现
Cache
接口import org.springframework.cache.Cache; import java.util.concurrent.Callable; public class MultiCache implements Cache { private String name; private Cache localCache; private Cache remoteCache; public MultiCache(String name, Cache localCache, Cache remoteCache) { this.name = name; this.localCache = localCache; this.remoteCache = remoteCache; } @Override public String getName() { return this.name; } @Override public Object getNativeCache() { return this; } @Override public ValueWrapper get(Object key) { ValueWrapper valueWrapper = localCache.get(key); if (valueWrapper == null) { valueWrapper = remoteCache.get(key); if (valueWrapper != null) { localCache.put(key, valueWrapper.get()); } } return valueWrapper; } @Override public <T> T get(Object key, Class<T> type) { T value = localCache.get(key, type); if (value == null) { value = remoteCache.get(key, type); if (value != null) { localCache.put(key, value); } } return value; } @Override public <T> T get(Object key, Callable<T> valueLoader) { ValueWrapper valueWrapper = localCache.get(key); if (valueWrapper == null) { T value = remoteCache.get(key, valueLoader); if (value != null) { localCache.put(key, value); } return value; } else { return (T) valueWrapper.get(); } } @Override public void put(Object key, Object value) { localCache.put(key, value); remoteCache.put(key, value); } @Override public ValueWrapper putIfAbsent(Object key, Object value) { localCache.putIfAbsent(key, value); return remoteCache.putIfAbsent(key, value); } @Override public void evict(Object key) { localCache.evict(key); remoteCache.evict(key); } @Override public void clear() { localCache.clear(); remoteCache.clear(); } }
-
实现
CacheManager
接口import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.support.AbstractCacheManager; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; public class MultiCacheManager extends AbstractCacheManager { private final CacheManager localCacheManger; private final CacheManager remoteCacheManager; public MultiCacheManager(CacheManager localCacheManager, CacheManager remoteManager) { this.localCacheManger = localCacheManager; this.remoteCacheManager = remoteManager; } @Override protected Collection<? extends Cache> loadCaches() { Set<String> localCacheNames = new HashSet<>(localCacheManger.getCacheNames()); Set<String> remoteCacheNames = new HashSet<>(remoteCacheManager.getCacheNames()); Collection<Cache> caches = new ArrayList<>(); localCacheNames.forEach(name -> { if (remoteCacheNames.contains(name)) { caches.add(new MultiCache(name, localCacheManger.getCache(name), remoteCacheManager.getCache(name))); } else { caches.add(localCacheManger.getCache(name)); } }); remoteCacheNames.forEach(name -> { if (!localCacheNames.contains(name)) { caches.add(remoteCacheManager.getCache(name)); } }); return caches; } }
-
使用自定义的缓存管理器
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.CaffeineSpec; import com.varyuan.awesome.cache.MultiCacheManager; import lombok.Getter; import lombok.Setter; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import java.time.Duration; import java.util.List; @Configuration @EnableConfigurationProperties(MultiCacheConfig.class) @ConfigurationProperties(prefix = "multi") @Setter @Getter // 两级缓存配置 public class MultiCacheConfig { private MultiCache local; private MultiCache remote; @Setter static class MultiCache { private String spec; private List<Config> settings; } @Setter static class Config { private String name; private String spec; } @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 使用Caffeine做本地缓存 // 支持配置最大容量,超时时间等,可参考com.github.benmanes.caffeine.cache.CaffeineSpec CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager(); // 设置公共配置 String localCommonSpec = local.spec; if (localCommonSpec != null && !localCommonSpec.isEmpty()) caffeineCacheManager.setCaffeineSpec(CaffeineSpec.parse(localCommonSpec)); for (Config item : local.settings) { String spec = item.spec; if (spec != null && !spec.isEmpty()) { // 单独配置cache com.github.benmanes.caffeine.cache.Cache<Object, Object> coffeeCache = Caffeine.from(item.spec).build(); caffeineCacheManager.registerCustomCache(item.name, coffeeCache); } else { // 使用公共配置创建Cache caffeineCacheManager.getCache(item.name); } } // 使用redis做远程缓存 // 只可以设置过期时间(单位S), 且不支持单独设置cache,可参考org.springframework.data.redis.cache.RedisCacheConfiguration String remoteSpec = remote.spec; long ttl = computeExpiration(remoteSpec); RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())).entryTtl(Duration.ofSeconds(ttl)); RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build(); for (Config item : remote.settings) { // 创建Cache redisCacheManager.getCache(item.name); } return new MultiCacheManager(caffeineCacheManager, redisCacheManager); } // 读取ttl private long computeExpiration(String spec) { String[] ttl = spec.split("=", -1); if (ttl.length == 2) { if ("ttl".equals(ttl[0])) { return Long.parseLong(ttl[1]); } } throw new IllegalArgumentException("error ttl param, please input like 'ttl=30'"); } }
cook
-
顶级接口
Cache
,CacheManager
UML图如下
-
spring.cache.type=simple
使用的是ConcurrentMapCacheManager, 而不是SimpleCacheManager -
redis6可以使用RESP3协议支持客户端缓存
-
redis连接池lettuce的池配置需要引入commons-pool2依赖