Spring Cache 配置多级缓存

两级缓存: 本地缓存使用Caffeine, 远程缓存使用redis. 也可以只使用本地缓存或者只使用redis缓存

github: https://github.com/varyuan/awesome-java

基于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>
  1. 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
    
  2. 实现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();
        }
    }
    
  3. 实现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;
        }
    }
    
  4. 使用自定义的缓存管理器

    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

  1. 顶级接口Cache, CacheManager UML图如下
    Cache
    CacheManager

  2. spring.cache.type=simple使用的是ConcurrentMapCacheManager, 而不是SimpleCacheManager

  3. redis6可以使用RESP3协议支持客户端缓存

  4. redis连接池lettuce的池配置需要引入commons-pool2依赖

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值