从架构角度结合分布式缓存和本地缓存

使用API统一分布式缓存和本地缓存

Posted by Dux on March 23, 2024

在工作中,有时候会碰到这样一种情况,一个产品本身就是以一个单体应用去开发设计的,但架不住规模的变化,需要调整为多节点集群部署。

那碰到这种既需要单节点部署,又需要多节点分布式部署的场景,缓存业务如何进行自适应调整

需求分析

面对这种场景,糙一点的方式无非是搞两套实现,一套是单节点部署,采用本地缓存,另一套采用分布式缓存,适用于多节点集群部署。

站在设计的角度上这很合理,但不够优雅。。。

愿景:把本地缓存和分布式缓存结合起来,提供统一的 API 去处理,在具体的场景中自适应具体的实现。

优点:

  • 统一实现,不必考虑部署场景
  • 底层实现更方便替换
  • 提供额外特性
    • 超时过期
    • 缓存空值
    • 初始化清理
  • …根据设计自行扩展

设计

  1. 定义缓存操作接口

    两个接口,分别应对于Map接口和常量接口

    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
    
    public interface AdaptingCache<K, V> {
       
        /**
         * 获取缓存名称
         *
         * @return 缓存名称
         */
        String getName();
       
        /**
         * 获取底层具体实现
         *
         * @return 底层具体实现,Redis为RedisCache
         */
        Cache getNativeCache();
       
        /**
         * 获取缓存值
         *
         * @param key 缓存key
         * @return 缓存值
         */
        V get(K key);
       
        /**
         * 获取缓存值
         *
         * @param key         缓存key
         * @param valueLoader 不存在则加载,并保存到缓存中
         * @return 缓存值
         */
        V get(K key, Callable<V> valueLoader);
       
        /**
         * 添加缓存
         *
         * @param key   缓存key
         * @param value 缓存值
         */
        void put(K key, @Nullable V value);
       
        /**
         * 添加缓存并返回
         *
         * @param key   缓存key
         * @param value 缓存值
         * @return 缓存值
         */
        default V putIfAbsent(K key, @Nullable V value) {
            V existingValue = get(key);
            if (existingValue == null) {
                put(key, value);
            }
            return existingValue;
        }
       
        /**
         * 移除缓存
         *
         * @param key 缓存key
         */
        void evict(K key);
       
        /**
         * 清理全部缓存
         */
        void clear();
       
    }
    
    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
    
    public interface AdaptingValueCache<V> {
       
        /**
         * 获取缓存名称
         *
         * @return 缓存名称
         */
        String getName();
       
        /**
         * 获取缓存值
         *
         * @return 缓存值
         */
        V get();
       
        /**
         * 获取缓存值
         *
         * @param valueLoader 若不存在则添加至缓存中
         * @return 缓存值
         */
        V get(Callable<V> valueLoader);
       
        /**
         * 添加缓存值
         *
         * @param value
         */
        void put(@Nullable V value);
       
        /**
         * 添加缓存值,并返回
         *
         * @param value 缓存值
         * @return 缓存值
         */
        default V putIfAbsent(@Nullable V value) {
            V existingValue = get();
            if (existingValue == null) {
                put(value);
            }
            return existingValue;
        }
       
        /**
         * 清理缓存
         */
        void clear();
       
    }
    
  2. 抽象桥接 Spring cache

    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
    
    public abstract class AbstractAdaptingCache<K, V> implements AdaptingCache<K, V> {
       
        protected Cache adapter;
       
        public AbstractAdaptingCache(Cache adapter) {
            this.adapter = adapter;
        }
       
        @Override
        public String getName() {
            return this.adapter.getName();
        }
       
        @Override
        public Cache getNativeCache() {
            return this.adapter;
        }
       
        @SuppressWarnings("unchecked")
        @Override
        public V get(K key) {
            Cache.ValueWrapper valueWrapper = this.adapter.get(key);
            if (valueWrapper != null) {
                return (V)valueWrapper.get();
            }
            return null;
        }
       
        @Override
        public V get(K key, Callable<V> valueLoader) {
            return this.adapter.get(key, valueLoader);
        }
       
        @Override
        public void put(K key, V value) {
            this.adapter.put(key, value);
        }
       
        @Override
        public void evict(K key) {
            this.adapter.evict(key);
        }
       
        @Override
        public void clear() {
            this.adapter.clear();
        }
       
    }
    
    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
    
    public abstract class AbstractAdaptingValueCache<V> implements AdaptingValueCache<V> {
       
        protected Cache adapter;
       
        public AbstractAdaptingValueCache(Cache adapter) {
            this.adapter = adapter;
        }
       
        @Override
        public String getName() {
            return this.adapter.getName();
        }
       
        @SuppressWarnings("unchecked")
        @Override
        public V get() {
            Cache.ValueWrapper valueWrapper = this.adapter.get(this.getName());
            if (valueWrapper != null) {
                return (V)valueWrapper.get();
            }
            return null;
        }
       
        @Override
        public V get(Callable<V> valueLoader) {
            return this.adapter.get(this.getName(), valueLoader);
        }
       
        @Override
        public void put(V value) {
            this.adapter.put(this.getName(), value);
        }
       
        @Override
        public void clear() {
            this.adapter.clear();
        }
       
    }
    
  3. 定义缓存提供者接口,用于提供底层实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    public interface CacheProvider {
       
        /**
         * 获取缓存
         *
         * @param cacheBuilder 缓存构建器
         * @return 缓存
         */
        <K, V> Cache getCache(CacheBuilder<K, V> cacheBuilder);
       
    }
    
  4. 缓存提供者具体实现,根据配置开启指定实现(本地缓存/分布式缓存)

    • 本地缓存,采用 Caffeign

      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
      
      public class MemoryCacheProvider implements CacheProvider {
           
          private final VillaCacheProperties cacheProperties;
           
          @Override
          public <K, V> Cache getCache(CacheBuilder<K, V> cacheBuilder) {
              Caffeine<Object, Object> newedBuilder = Caffeine.newBuilder();
              String name = cacheBuilder.getName();
              boolean isConfig = cacheProperties.getNameConfigs().containsKey(name);
              boolean cacheNullValues = cacheBuilder.isCacheNullValues();
              Duration ttl = cacheBuilder.getTtl();
              if (isConfig) {
                  VillaCacheProperties.NameConfig nameConfig = cacheProperties.getNameConfigs().get(name);
                  ttl = nameConfig.getTtl();
                  cacheNullValues = nameConfig.isCacheNullValues();
                  cacheBuilder.initializeClear(nameConfig.isInitializeClear());
              }
              if (ttl != null) {
                  newedBuilder.expireAfterWrite(ttl);
              }
              com.github.benmanes.caffeine.cache.Cache<Object, Object> cache = newedBuilder.build();
              return new CaffeineCache(name, cache, cacheNullValues);
          }
           
      }
      
    • 分布式缓存,采用 Redis

      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
      
      public class RedisCacheProvider implements CacheProvider {
           
          private final RedisConnectionFactory redisConnectionFactory;
           
          private final CacheProperties cacheProperties;
           
          private final ResourceLoader resourceLoader;
           
          private final VillaCacheProperties villaCacheProperties;
           
          @Override
          public <K, V> Cache getCache(CacheBuilder<K, V> cacheBuilder) {
              String name = cacheBuilder.getName();
              Duration ttl = cacheBuilder.getTtl();
              boolean cacheNullValues = cacheBuilder.isCacheNullValues();
              boolean isConfig = villaCacheProperties.getNameConfigs().containsKey(name);
              if (isConfig) {
                  VillaCacheProperties.NameConfig nameConfig = villaCacheProperties.getNameConfigs().get(name);
                  ttl = nameConfig.getTtl();
                  cacheNullValues = nameConfig.isCacheNullValues();
                  cacheBuilder.initializeClear(nameConfig.isInitializeClear());
              }
              RedisCacheConfiguration redisCacheConfiguration = this.createConfiguration(cacheProperties, resourceLoader.getClassLoader());
              if (!cacheNullValues) {
                  redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
              }
              if (ttl != null) {
                  redisCacheConfiguration = redisCacheConfiguration.entryTtl(ttl);
              }
              return this.createRedisCache(name, redisCacheConfiguration);
          }
           
          protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
              RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory);
              if (cacheConfig != null) {
                  builder.cacheDefaults(cacheConfig);
              }
              if (cacheProperties.getRedis().isEnableStatistics()) {
                  builder.enableStatistics();
              }
              return (RedisCache)builder.build().getCache(name);
          }
           
          private org.springframework.data.redis.cache.RedisCacheConfiguration createConfiguration(
                  CacheProperties cacheProperties, ClassLoader classLoader) {
              CacheProperties.Redis redisProperties = cacheProperties.getRedis();
              org.springframework.data.redis.cache.RedisCacheConfiguration config = org.springframework.data.redis.cache.RedisCacheConfiguration
                      .defaultCacheConfig();
              config = config
                      .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
              if (redisProperties.getTimeToLive() != null) {
                  config = config.entryTtl(redisProperties.getTimeToLive());
              }
              if (redisProperties.getKeyPrefix() != null) {
                  config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
              }
              if (!redisProperties.isCacheNullValues()) {
                  config = config.disableCachingNullValues();
              }
              if (!redisProperties.isUseKeyPrefix()) {
                  config = config.disableKeyPrefix();
              }
              return config;
          }
           
      }
      
  5. 定义构建器,用于构建缓存

    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
    
    public class CacheBuilder<K, V> {
       
        private String name;
       
        private Duration ttl;
       
        private boolean cacheNullValues = true;
       
        private boolean initializeClear = false;
       
        public static CacheBuilder<Object, Object> newBuilder() {
            return new CacheBuilder<>();
        }
       
        /**
         * 缓存名称
         *
         * @param name 缓存名称
         * @return this
         */
        public CacheBuilder<K, V> name(String name) {
            this.name = name;
            return this;
        }
       
        /**
         * 初始化时清理数据
         *
         * @return this
         */
        public CacheBuilder<K, V> initializeClear(boolean initializeClear) {
            this.initializeClear = initializeClear;
            return this;
        }
       
        /**
         * 设置超时时间,超时自动剔除
         *
         * @param ttl 超时时间
         * @return this
         */
        public CacheBuilder<K, V> ttl(Duration ttl) {
            this.ttl = ttl;
            return this;
        }
       
        /**
         * 缓存null值
         *
         * @param cacheNullValues 是否允许缓存空值
         * @return this
         */
        public CacheBuilder<K, V> cacheNullValues(boolean cacheNullValues) {
            this.cacheNullValues = cacheNullValues;
            return this;
        }
       
        /**
         * 构建缓存对象,Map结构
         *
         * @param cacheProvider 缓存提供者
         * @return 缓存对象,Map结构
         */
        public <K1 extends K, V1 extends V> AdaptingCache<K1, V1> build(CacheProvider cacheProvider) {
       
            Assert.state(name != null, "CacheBuild name must not be null.");
       
            AdaptingCache<K1, V1> cache = new AbstractAdaptingCache<K1, V1>(cacheProvider.getCache(this)) {
            };
            if (BooleanUtils.isTrue(initializeClear)) {
                cache.clear();
            }
            return cache;
        }
       
        /**
         * 构建缓存对象,Value结构
         *
         * @param cacheProvider 缓存提供者
         * @return 缓存对象,Value结构
         */
        public <V1 extends V> AdaptingValueCache<V1> buildValue(CacheProvider cacheProvider) {
       
            Assert.state(name != null, "CacheBuild name must not be null.");
       
            AdaptingValueCache<V1> valueCache = new AbstractAdaptingValueCache<V1>(cacheProvider.getCache(this)) {
            };
            if (BooleanUtils.isTrue(initializeClear)) {
                valueCache.clear();
            }
            return valueCache;
        }
       
        /**
         * 获取缓存名称
         *
         * @return 缓存名称
         */
        public String getName() {
            return name;
        }
       
        /**
         * 获取缓存超时时间
         *
         * @return 超时时间
         */
        public Duration getTtl() {
            return ttl;
        }
       
        /**
         * 是否允许缓存空值
         *
         * @return 允许则为true
         */
        public boolean isCacheNullValues() {
            return cacheNullValues;
        }
       
    }
    

示例

改造前

1
2
3
4
5
6
7
class Main{
    
    private Map<String,String> cache;
    
    private String[] values;
    
}

改造后

1
2
3
4
5
6
7
8
9
10
11
12
class Main{

    private AdaptingCache<String,String> cache;

    private AdaptingValueCache<String[]> values;
    
    public Main(CacheProvider cacheProvider){
        cache = CacheBuilder.newBuilder().name("cache").build(cacheProvider);
        values =CacheBuilder.newBuilder().name("values").buildValue(cacheProvider);
    }
    
}

拓展

架构设计没有统一的标准,每个人都有自己的设计理念。

但奔向的目标是一致的,让产品更方便的使用、让后期维护和拓展更加容易…

如果你有什么新的想法,评论区见