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

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

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

需求分析

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

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

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

优点:

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

设计

  1. 定义缓存操作接口

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

    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();
    
    }
    
    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

    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();
        }
    
    }
    
    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. 定义缓存提供者接口,用于提供底层实现

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

    • 本地缓存,采用 Caffeign

      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

      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. 定义构建器,用于构建缓存

    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;
        }
    
    }
    

示例

改造前

class Main{
    
    private Map<String,String> cache;
    
    private String[] values;
    
}

改造后

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);
    }
    
}

拓展

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

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

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

相关推荐

  1. 架构角度结合分布式缓存本地缓存

    2024-03-24 08:26:05       17 阅读
  2. 架构设计 - 本地热点缓存

    2024-03-24 08:26:05       5 阅读
  3. 分布式本地缓存刷新-日常笔记

    2024-03-24 08:26:05       8 阅读
  4. 分布式缓存

    2024-03-24 08:26:05       8 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-24 08:26:05       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-24 08:26:05       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-24 08:26:05       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-24 08:26:05       18 阅读

热门阅读

  1. python函数

    2024-03-24 08:26:05       19 阅读
  2. 继承和深拷贝封装

    2024-03-24 08:26:05       20 阅读
  3. 大模型: 提示词工程(prompt engineering)

    2024-03-24 08:26:05       18 阅读
  4. JVM学习

    JVM学习

    2024-03-24 08:26:05      17 阅读
  5. 【测试思考】设计测试用例时,你在想什么

    2024-03-24 08:26:05       18 阅读
  6. Electron IPC通信机制深度解析与实例演示

    2024-03-24 08:26:05       17 阅读
  7. 如何系统地自学 Python?

    2024-03-24 08:26:05       15 阅读
  8. 学习资料记录

    2024-03-24 08:26:05       17 阅读
  9. 20 有效的括号

    2024-03-24 08:26:05       18 阅读
  10. 机器翻译评价指标 BLEU分数

    2024-03-24 08:26:05       21 阅读
  11. Day31 贪心算法

    2024-03-24 08:26:05       17 阅读
  12. Ubuntu 22.04 安装配置时间同步服务器

    2024-03-24 08:26:05       19 阅读
  13. Eureka和Nacos的关系

    2024-03-24 08:26:05       17 阅读