【代码重构设计模式之运用】之基于google guava和redis的一二级缓存设计实现

我们通常在接口QPS特别比较高的情况下,为了减少对数据库的频繁查询,会引入缓存,以提高接口查询性能。但是对于缓存,为了减少对Redis耦合依赖,以进一步减少网络调用,通常又会引入一级缓存,这样一二级缓存双保险可以避免缓存击穿而带来数据库QPS瓶颈而带来的灾难。而本文采用基于google guava(一级缓存)+redis(二级缓存)设计实现,
同时为了后期扩展性一级可维护性,即所谓的“开闭“原则,采用了设计模式并设计了这套一二级缓存实现。

1.UML类图介绍

UML图

根据如上UML图,我们可以梳理如下内容。
1、InvalidateCommand提供了对一二级缓存失效命令接口。
2、CacheManager,提供了每个缓存管理器的开关,并提供了一二级缓存方法由子类实现。
3、SecondaryCache是二级缓存接口,提供了根据Redis Hash存储结构的get/set方法。
4、RedisSecondaryCache实现了二级缓存接口,提供了基于Hash存储功能。
5、AbstractCacheManager是一个抽象缓存管理器,封装了基于一二级缓存的回调调用。
6、SimpleDistrictCacheFactory是一个缓存容器工厂,维护两套不同数据源。
7、GuavaCacheConfig是一个一级缓存配置类。
8、SimpleDistrictCacheManager是一个面向业务功能的缓存管理器,这样对于Controller可以直接调用改缓存管理器。
其内部封装了对一二级缓存的生命周期设置,以及对数据库Service层的依赖调用。可以说,对于接口查询,承担着一个代理类作用,数据库查询、一二级缓存调用都委托了该管理器。

从“开闭原则”来讲。
1、基于SecondaryCache接口可以实现基于其他分布式缓存中间件。
2、SimpleDistrictCacheFactory对外部提供了两套不同数据一级缓存容器。
3、AbstractCacheManager抽象管理器封装了对于一二级缓存一级查询库操作的回调机制,其原理是,如果一级缓存没有,则从二级缓存获取,二级缓存没有则调用回调接口,获取查询库的数据;同时并设置到一二级缓存中,这样对于下一次请求就会从缓存中获取,这个行为目前是一个延迟加载机制。
(1)、抽象类实现了二级缓存接口secondaryCache(),直接从SecondaryCacheFactory.create(RedisSecondaryCache.class)获取。
(2)、抽象类并没有直接实现了一级缓存接口,而是由子类实现。

2.相关接口以及类介绍

2.1 InvalidateCommand

/**
 * @description: 缓存失效命令接口
 * @Date : 2019/5/10 下午1:54
 * @Author : 石冬冬-Seig Heil
 */
public interface InvalidateCommand<K,HK> {
    /**
     * 清除指定k的hk对应的值
     * @param k
     * @param hk
     */
    void invalidate(K k,HK hk);
    /**
     * 清除指定k所有缓存
     * @param k
     */
    void invalidateAll(K k);
}

2.2 SecondaryCache

/**
 * @description: 二级缓存接口
 * @Date : 2019/5/10 上午11:53
 * @Author : 石冬冬-Seig Heil
 */
public interface SecondaryCache extends InvalidateCommand{
    /**
     * 从二级缓存获取值
     * @param key 小key
     * @param cacheKey 缓存key
     * @return
     */
    String getValueFromSecondary(String key,String cacheKey);
    /**
     * 设置二级缓存值
     * @param key 小key
     * @param cacheKey 缓存key
     * @param expireSeconds 缓存失效时间(单位:秒)
     * @param value 需要刷进缓存的回调接口值
     */
    <T> void setValueForSencondary(String key,String cacheKey,long expireSeconds,Optional<T> value);
}

2.3 RedisSecondaryCache

/**
 * @description: Redis二级缓存
 * @Date : 2019/5/10 上午11:59
 * @Author : 石冬冬-Seig Heil
 */
public class RedisSecondaryCache implements SecondaryCache{

    @Autowired
    RedisService redisService;

    @Override
    public String getValueFromSecondary(String key,String cacheKey) {
        Object hashValue = redisService.hashOperations().get(cacheKey,key);
        return null == hashValue ? null : hashValue.toString();
    }

    @Override
    public <T> void setValueForSencondary(String key,String cacheKey,long expireSeconds, Optional<T> value) {
        redisService.hashOperations().put(cacheKey,key, JSONObject.toJSONString(value));
        redisService.hashOperations().getOperations().expire(cacheKey,expireSeconds, TimeUnit.SECONDS);
    }

    @Override
    public void invalidate(Object o, Object o2) {
        redisService.hashOperations().delete(o.toString(),o2.toString());
    }

    @Override
    public void invalidateAll(Object o) {
        redisService.del(o.toString());
    }
}

2.4 AbstractCacheManager

/**
 * @description: 抽象缓存管理器
 * @Date : 2019/4/22 下午6:37
 * @Author : 石冬冬-Seig Heil
 */
@Slf4j
public abstract class AbstractCacheManager<K,HK,V> implements CacheManage<K,HK,V> {
    /**
     * 开关
     */
    final TypeReference<DiamondConfig.CacheEnable> REFERENCE = new TypeReference<DiamondConfig.CacheEnable>(){};
    /**
     * 空值
     */
    final Optional EMPTY = Optional.empty();

    @Autowired
    RedisService redisService;

    @Autowired
    DiamondConfig diamondConfig;

    @Autowired
    SecondaryCache secondaryCache;

    /**
     * 从二级缓存 加载数据,二级缓存没有值则调用 callback 从数据库读取并刷新到二级缓存
     * @param context
     * @param <T>
     * @return
     * */
    protected <T> T getFromSecondary(CacheContext<T> context){
        String key = context.key;
        String cacheKey = cacheKey();
        Optional<T> dbValue = EMPTY;
        try {
            String secondaryValue = secondaryCache.getValueFromSecondary(key,cacheKey);
            log.debug("getFromSecondary,key={},secondaryValue={}",cacheKey,secondaryValue);
            if(null != secondaryValue){
                return JSONObject.parseObject(secondaryValue,context.reference);
            }
            dbValue = context.callback.get();
            if(null == dbValue || !dbValue.isPresent()){
                log.debug("getFromDB,shortName={},hashKey={},not callback",shortName(),key);
                return (T)EMPTY.get();
            }
            log.debug("getFromDB,shortName={},hashKey={},dbValue={}",shortName(),key,JSONObject.toJSONString(dbValue.get()));
            secondaryCache.setValueForSencondary(key,cacheKey,context.expireSeconds,dbValue);
            return dbValue.get();
        } catch (Exception e) {
            log.error("getFromSecondary exception,shortName={},hashKey={}",shortName(),key,e);
            return null == dbValue ? (T)EMPTY.get() : dbValue.get();
        }
    }


    /**
     * 获取缓存开关配置
     * @return
     */
    protected DiamondConfig.CacheEnable cacheSwitch(){
        return diamondConfig.of(diamondConfig.cacheSwitch,REFERENCE);
    }

    @Override
    public SecondaryCache secondaryCache() {
        return SecondaryCacheFactory.create(RedisSecondaryCache.class);
    }

    @Override
    public String cacheKey() {
        return redisService.getKeyWithSystemCode(shortName());
    }

    @Override
    public void invalidate(Object o, Object o2) {
        primaryCache().invalidate(o);
        secondaryCache().invalidate(redisService.getKeyWithSystemCode(o.toString()),o2);
    }

    @Override
    public void invalidateAll(Object o) {
        primaryCache().invalidateAll();
        secondaryCache().invalidateAll(redisService.getKeyWithSystemCode(o.toString()));
    }

    @Override
    public boolean useCache() {
        return true;
    }

    @Override
    public Map<String, V> showAsMap() {
        Map<String,V> newMap = Maps.newHashMap();
        primaryCache().asMap().entrySet().forEach(entry -> newMap.put(Objects.toString(entry.getKey()),entry.getValue()));
        return newMap;
    }

    @Override
    public Set<K> keys() {
        return primaryCache().asMap().keySet();
    }
}

2.5 CacheContext

/**
 * @description: 缓存上下文对象
 * @Date : 2019/5/10 下午12:05
 * @Author : 石冬冬-Seig Heil
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CacheContext<T> {
    /**
     * 缓存key
     */
    String key;
    /**
     * 转换类型
     * 这里指缓存转换成JSON字符串存储,但是取出意味着需要转换对应的类类型
     */
    TypeReference<T> reference;
    /**
     * 回调函数,该回调接口意味着如果二级缓存没有将会通过查询db方式获取回调结果刷新到二级缓存中
     */
    Supplier<Optional<T>> callback;
    /**
     * 二级缓存失效时间(单位:秒)
     */
    long expireSeconds;
}

2.6 SimpleDistrictCacheManager

/**
 * @description: 行政编码-缓存管理器
 * @Date : 2019/5/5 下午5:32
 * @Author : 石冬冬-Seig Heil
 */
@Component
@Slf4j
public class SimpleDistrictCacheManager extends AbstractCacheManager<String,String,Result<List<SimpleDistrictRe>>> {

    @Autowired
    DiamondConfig diamondConfig;

    @Autowired
    DictionaryRegFacade dictionaryRegFacade;
    /**
     * short name
     */
    final String SHORT_NAME = CacheShortName.simpleDistrictCache.name();
    /**
     * 省份列表缓存key
     */
    final String PROVINCE_CACHE_KEY = "provinceCache";
    /**
     * 默认失效时间-2小时(单位秒)
     */
    final long DEFAULT_EXPIRE_SECONDS = 7200;

    /**
     * 查询省份列表
     * @return
     */
    public Result<List<SimpleDistrictRe>> queryProvinces(){
        if(!useCache()){
            return dictionaryRegFacade.queryProvinces();
        }
        Result<List<SimpleDistrictRe>> queryResult;
        try {
            CacheContext<Result<List<SimpleDistrictRe>>> context = CacheContext.<Result<List<SimpleDistrictRe>>>builder()
                    .key(PROVINCE_CACHE_KEY).reference(TypeReferences.SIMPLE_DISTRICT_TYPE).expireSeconds(DEFAULT_EXPIRE_SECONDS)
                    .callback(() -> Optional.ofNullable(dictionaryRegFacade.queryProvinces())).build();
            queryResult = primaryCache().get(PROVINCE_CACHE_KEY,() -> super.getFromSecondary(context));
        } catch (ExecutionException e) {
            log.info("{} focus on an exception,then execute queryDB",SHORT_NAME,e);
            queryResult = dictionaryRegFacade.queryProvinces();
            log.info("{} focus on an exception,then execute value={}",SHORT_NAME,JSONObject.toJSONString(queryResult));
        }
        return queryResult;
    }

    @Override
    public Cache<String, Result<List<SimpleDistrictRe>>> primaryCache() {
        return SimpleDistrictCacheFactory.get();
    }

    @Override
    public boolean useCache() {
        boolean useCache = cacheSwitch().simpleDistrictEnable;
        log.debug("{} useCache={}",SHORT_NAME,useCache);
        return useCache;
    }

    @Override
    public String shortName() {
        return SHORT_NAME;
    }

}

2.7 GuavaCacheConfig

@Configuration
@Slf4j
public class GuavaCacheConfig {
    /**
     * 初始化缓存相关参数
     * 行政编码-缓存配置(直营)
     * @return
     */
    @Bean
    Cache<String,Result<List<SimpleDistrictRe>>> simpleDistrictCacheZy(){
        log.info("start build simpleDistrictCacheZy arguments");
        return CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后6小时过期
                .expireAfterWrite(1, TimeUnit.HOURS)
                //设置缓存容器的初始容量为10
                .initialCapacity(32)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(20)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(notification -> log.info("simpleDistrictCacheZy remove cache,key={},cause={}",notification.getKey(),notification.getCause()))
                .build();
    }

    /**
     * 初始化缓存相关参数
     * 行政编码-缓存配置(渠道)
     * @return
     */
    @Bean
    Cache<String,Result<List<SimpleDistrictRe>>> simpleDistrictCacheQd(){
        log.info("start build simpleDistrictCacheQd arguments");
        return CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置写缓存后6小时过期
                .expireAfterWrite(1, TimeUnit.HOURS)
                //设置缓存容器的初始容量为10
                .initialCapacity(32)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(20)
                //设置要统计缓存的命中率
                .recordStats()
                //设置缓存的移除通知
                .removalListener(notification -> log.info("simpleDistrictCacheQd remove cache,key={},cause={}",notification.getKey(),notification.getCause()))
                .build();
    }
}

2.8 DictionaryRegController

/**
 * @description: 行政编码接口服务
 * @Date : 2019/5/5 下午4:11
 * @Author : 石冬冬-Seig Heil
 */
@RestController
@RequestMapping("/dictionaryReg")
@Api(description = "行政编码",tags = "行政编码")
public class DictionaryRegController implements DictionaryRegProvider {

    final String LOG_TITLE = "【行政编码】";

    @Autowired
    SimpleDistrictCacheManager simpleDistrictCacheManager;

    /**
     * 查询省份列表
     * @return
     */
    @GetMapping("/queryProvinces")
    @ApiOperation("查询省份列表")
    @LogMonitor(value = LOG_TITLE, action = @Action("查询省份列表"))
    @Override
    public Result<List<SimpleDistrictRe>> queryProvinces() {
        return simpleDistrictCacheManager.queryProvinces();
    }

}
发布了46 篇原创文章 · 获赞 27 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/shichen2010/article/details/90317101
今日推荐