在工作中,有时候会碰到这样一种情况,一个产品本身就是以一个单体应用去开发设计的,但架不住规模的变化,需要调整为多节点集群部署。
那碰到这种既需要单节点部署,又需要多节点分布式部署的场景,缓存业务如何进行自适应调整
需求分析
面对这种场景,糙一点的方式无非是搞两套实现,一套是单节点部署,采用本地缓存,另一套采用分布式缓存,适用于多节点集群部署。
站在设计的角度上这很合理,但不够优雅。。。
愿景:把本地缓存和分布式缓存结合起来,提供统一的 API 去处理,在具体的场景中自适应具体的实现。
优点:
- 统一实现,不必考虑部署场景
- 底层实现更方便替换
- 提供额外特性
- 超时过期
- 缓存空值
- 初始化清理
- ...
- ...根据设计自行扩展
设计
-
定义缓存操作接口
两个接口,分别应对于Map接口和常量接口
javapublic 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(); }
javapublic 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(); }
-
抽象桥接 Spring cache
javapublic 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(); } }
javapublic 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(); } }
-
定义缓存提供者接口,用于提供底层实现
javapublic interface CacheProvider { /** * 获取缓存 * * @param cacheBuilder 缓存构建器 * @return 缓存 */ <K, V> Cache getCache(CacheBuilder<K, V> cacheBuilder); }
-
缓存提供者具体实现,根据配置开启指定实现(本地缓存/分布式缓存)
-
本地缓存,采用 Caffeign
javapublic 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
javapublic 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; } }
-
-
定义构建器,用于构建缓存
javapublic 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);
}
}
拓展
架构设计没有统一的标准,每个人都有自己的设计理念。
但奔向的目标是一致的,让产品更方便的使用、让后期维护和拓展更加容易...
如果你有什么新的想法,评论区见