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

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

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

需求分析

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

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

愿景:把本地缓存和分布式缓存结合起来,提供统一的 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);
    }
    
}

拓展

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

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

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

相关推荐
WX187021128736 小时前
在分布式光伏电站如何进行电能质量的治理?
分布式
不能再留遗憾了9 小时前
RabbitMQ 高级特性——消息分发
分布式·rabbitmq·ruby
茶馆大橘9 小时前
微服务系列六:分布式事务与seata
分布式·docker·微服务·nacos·seata·springcloud
58沈剑10 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构
材料苦逼不会梦到计算机白富美12 小时前
golang分布式缓存项目 Day 1
分布式·缓存·golang
想进大厂的小王12 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
Java 第一深情12 小时前
高性能分布式缓存Redis-数据管理与性能提升之道
redis·分布式·缓存
HBryce2413 小时前
缓存-基础概念
java·缓存
阿伟*rui13 小时前
认识微服务,微服务的拆分,服务治理(nacos注册中心,远程调用)
微服务·架构·firefox
ZHOU西口14 小时前
微服务实战系列之玩转Docker(十八)
分布式·docker·云原生·架构·数据安全·etcd·rbac