文章目录
- 1.UML类图介绍
- 2.相关接口以及类介绍
- 2.1 InvalidateCommand
- 2.2 SecondaryCache
- 2.3 RedisSecondaryCache
- 2.4 AbstractCacheManager
- 2.5 CacheContext
- 2.6 SimpleDistrictCacheManager
- 2.7 GuavaCacheConfig
- 2.8 DictionaryRegController
我们通常在接口QPS特别比较高的情况下,为了减少对数据库的频繁查询,会引入缓存,以提高接口查询性能。但是对于缓存,为了减少对Redis耦合依赖,以进一步减少网络调用,通常又会引入一级缓存,这样一二级缓存双保险可以避免缓存击穿而带来数据库QPS瓶颈而带来的灾难。而本文采用基于google guava(一级缓存)+redis(二级缓存)设计实现,
同时为了后期扩展性一级可维护性,即所谓的“开闭“原则,采用了设计模式并设计了这套一二级缓存实现。
1.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();
}
}