增加缓存是一种有利于系统的高性能的手段,应用集群可以的水平扩容,但是RDB由于天生的一些缺陷无法无法做到,NoSQL虽然天生支持集群,但是对事务的支持也是一大短板,因此在系统架构设计的过程中,DB资源是十分宝贵的,尤其是快速迭代的大型分布式系统。
- 经过前一篇文章的沉淀,我们把系统中的慢SQL迁移到备库去了,下一个阶段,我们是异步缓存,水平拆库,从设计上降低DB的负载。此篇文章总结一下博主开发的一个相对通用的缓存组件。
业务场景仍然是我们分为核心链路和非核心链路的报表展示,现在第一版只是把慢SQL迁移到了备库,但是很多对实时性要求并不高的应用仍然每次请求都能够打到DB,这是没必要的,这个时候缓存就非常有必要但是缓存,当然我们要做一个比较通用的缓存设计肯定不是简单去对应的代码片段去改一下,查询缓存是否存在,如果存在就返回,或则查DB。
逻辑时序图如下:
这个逻辑其实是脱离于业务场景的横向逻辑,给每一个需要缓存的操作一个代理,是不是很容易想到代理设计模式.但是这种不太适合写静态代理,因为如果每个需要缓存的类都去写一个代理未免太难维护了,基于Java反射可以去实现动态代理。定义AOP切面是一个很合适的方式,但是还有几个问题:
- 如何灵活去替换不同的缓存组件,例如redis或则Guava或则其他各种缓存。
- 如何最大的降低对业务代码的侵入性,方便又快捷的接入缓存组件
- 如何让同样的参数逻辑上映射出相同的缓存参数Key.
1.第一个问题可以定义一个缓存组件的接口,组件可以给一个默认的实现(加快开发效率),也可以使用组件的人自行去扩展对应的缓存组件。而组件的缓存逻辑只需要使用接口即可,隔离变化与稳定永远是设计模式最大的价值。
2.降低业务代码的入侵,需要使用缓存的操作,具体就是某个函数,可以使用一个注解标记元数据,该元数据可以定义该函数的参数key缓存的时间等等信息,这样切面可以去做更多的事情。
3.如何让相同的参数缓存同样的key,这个可能考虑到具体使用方的场景,这个可以给一个默认实现,但是不能写死,对于此处可以给出一个默认实现,但是一定可以给开发者扩展的可能,例如可能某个需求需要支持,无论参数如何变化都缓存一样的值。
解决了上面的问题,talk is cheap, show me the code.
/**
* 缓存接口定义, 可以实现自己的缓存,例如Redis, Tair等等
*/
public interface CacheLoader {
/**
* 获取缓存
* @param key
* @return
*/
Object get(String key);
/**
* 设置缓存与超时时间
* @param key
* @param val
* @param cacheTime
* @param timeUnit
* @return
*/
boolean set(String key, Object val, Integer cacheTime, TimeUnit timeUnit);
}
缓存核心模板类:
public abstract class EnableCacheAspect {
Logger logger = Logger.getLogger("EnableCacheAspect");
protected CacheLoader cacheLoader;
/**
* 定义切面为处理com.micro.cache.annotation.EnableCache注解
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(com.micro.cache.annotation.EnableCache)")
public Object processCache(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object val = null;
try {
boolean isMethod = proceedingJoinPoint.getSignature() instanceof MethodSignature;
if (!isMethod) {
// 暂时不支持非方法级别
return proceedingJoinPoint.proceed();
}
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
EnableCache enableCache = methodSignature.getMethod().getAnnotation(EnableCache.class);
if (enableCache == null) {
return proceedingJoinPoint.proceed();
}
CacheLoader cacheLoader = getCacheLoader();
String cacheKey = getCacheKey(proceedingJoinPoint);
formatLog("method -> %s get from cache", cacheKey);
val = cacheLoader.get(cacheKey);
if (val == null) {
formatLog("method -> %s not exits in cache ,put key", cacheKey);
val = proceedingJoinPoint.proceed();
boolean cacheSuccess = cacheLoader.set(cacheKey, val, enableCache.cacheTimes(), enableCache.timeUnit());
if (!cacheSuccess) {
cacheFail(cacheKey, val, enableCache.cacheTimes(), enableCache.timeUnit());
}
} else {
formatLog("method -> %s in cache val -> %s", cacheKey, cacheKey);
}
} catch (Throwable throwable) {
processThrowable(throwable);
throw throwable;
}
return val;
}
// 处理异常信息
protected void processThrowable(Throwable throwable) {
}
// 缓存设置失败
protected void cacheFail(String cacheKey, Object val, int times, TimeUnit timeUnit) {
// sub class process
}
// 此处映射切点的信息与key的关系,protected可以让子类去override
protected String getCacheKey(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = signature.getMethod();
String beanName = proceedingJoinPoint.getTarget().getClass().getSimpleName();
StringBuilder key = new StringBuilder();
for(Object param : proceedingJoinPoint.getArgs()) {
key.append(param).append(getKeySplitor());
}
return beanName + "." + method.getName() + getKeySplitor() + key.subSequence(0, key.length() - 1).toString();
}
protected String getKeySplitor() {
return "#";
}
// 具体使用的缓存中间件不定义
protected abstract CacheLoader getCacheLoader();
protected void formatLog(String content, String... params) {
logger.log(Level.ALL, String.format(content, params));
}
}
需要支持缓存的函数注解:
/**
* 支持缓存注解
* @author micro
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableCache {
/**
* 缓存的时间,默认60
* @return
*/
int cacheTimes() default 60;
/**
* 对应缓存时间单位 默认秒
* @return
*/
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
这里的CacheLoader我有一个默认的实现,永久不超时:
/**
* Created by micro on 21/07/2018.
* 默认的缓存实现, 永久不超时
*/
@Deprecated
public class MapCacheLoader implements CacheLoader {
private Map<String, Object> cacheMap = Maps.newHashMap();
public Object get(String key) {
return cacheMap.get(key);
}
public boolean set(String key, Object val, Integer cacheTime, TimeUnit timeUnit) {
this.cacheMap.put(key, val);
return Boolean.TRUE;
}
}
@Component
@Aspect
public class MapEnableCacheAspect extends EnableCacheAspect {
protected CacheLoader getCacheLoader() {
if (this.cacheLoader == null) {
synchronized (this) {
if (this.cacheLoader == null) {
this.cacheLoader = new MapCacheLoader();
return this.cacheLoader;
}
return this.cacheLoader;
}
} else {
return this.cacheLoader;
}
}
}
测试类:
@RunWith(SpringRunner.class)
@Import(MyConfig.class)
public class CacheAspectTest {
@Autowired
private AspectJProxyFactory aspectJProxyFactory;
@Test
public void testCache() {
Cache cache = aspectJProxyFactory.getProxy();
for (int i = 0;i < 10;i ++) {
cache.doSomething("hello");
}
}
}
interface Cache {
@EnableCache(cacheTimes = 1, timeUnit = TimeUnit.HOURS)
Object doSomething(String params);
}
class BeanPrintPostBeanProccessor implements BeanPostProcessor {
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("------------initializing bean name : " + beanName + "--------------");
return null;
}
}
@Configuration
class MyConfig {
@Bean
public EnableCacheAspect mapEnableCacheAspect() {
return new MapEnableCacheAspect();
}
@Bean
public Cache cache() {
return new Cache() {
// 方法注解不继承
@EnableCache(cacheTimes = 1, timeUnit = TimeUnit.HOURS)
public Object doSomething(String params) {
System.out.println("执行cacheMethod");
return "dbInfo + " + params;
}
};
}
@Bean
public BeanPostProcessor BeanPrintPostBeanProccessor() {
return new BeanPrintPostBeanProccessor();
}
@Bean
@Autowired
public AspectJProxyFactory aspectJProxyFactory(Cache cache) {
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(cache);
aspectJProxyFactory.addAspect(new MapEnableCacheAspect());
return aspectJProxyFactory;
}
}