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

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

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

需求分析

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

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

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

优点:

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

设计

  1. 定义缓存操作接口

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

    java 复制代码
    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();
    
    }
    java 复制代码
    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

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

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

    • 本地缓存,采用 Caffeign

      java 复制代码
      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

      java 复制代码
      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. 定义构建器,用于构建缓存

    java 复制代码
    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;
        }
    
    }

示例

改造前

java 复制代码
class Main{
    
    private Map<String,String> cache;
    
    private String[] values;
    
}

改造后

java 复制代码
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);
    }
    
}

拓展

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

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

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

相关推荐
Lee川15 分钟前
深度拆解:基于面向对象思维的“就地编辑”组件全模块解析
javascript·架构
勤劳打代码16 分钟前
Flutter 架构日记 — 状态管理
flutter·架构·前端框架
子兮曰6 小时前
后端字段又改了?我撸了一个 BFF 数据适配器,从此再也不怕接口“屎山”!
前端·javascript·架构
卓卓不是桌桌8 小时前
如何优雅地处理 iframe 跨域通信?这是我的开源方案
javascript·架构
Qlly8 小时前
DDD 架构为什么适合 MCP Server 开发?
人工智能·后端·架构
用户881586910911 天前
AI Agent 协作系统架构设计与实践
架构
鹏北海1 天前
Qiankun 微前端实战踩坑历程
前端·架构
货拉拉技术1 天前
货拉拉海豚平台-大模型推理加速工程化实践
人工智能·后端·架构
茶杯梦轩1 天前
从零起步学习RabbitMQ || 第三章:RabbitMQ的生产者、Broker、消费者如何保证消息不丢失(可靠性)详解
分布式·后端·面试
RoyLin1 天前
libkrun 深度解析:架构设计、模块实现与 Windows WHPX 后端
架构