[调优]缓存处理逻辑优化
缓存问题
优化解决三个问题:
- 缓存逻辑抽离业务逻辑,以缓存切面形式进行拦截和过滤
- 缓存穿透、缓存击穿
- 缓存与数据库的数据一致性
缓存过滤
- ① 通过spel进行缓存key动态解析
- ② 缓存穿透保护。 缓存无数据则第一时间设置empty,防止流量击穿到db
- ③ 分布式锁进行db数据请求控制,防止db击穿
- ④ 支持分布式锁竞态条件下异步延迟获取数据结果
代码实现
/**
* @author: guanjian
* @date: 2020/8/5 9:28
* @description: 缓存过滤切面
*/
@Aspect
@Component("cacheFilterAspect")
public class CacheFilterAspect {
private final static Logger LOGGER = LoggerFactory.getLogger(CacheFilterAspect.class);
/**
* spEl parser
*/
private final static ExpressionParser parser = new SpelExpressionParser();
private final static ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
@Resource
private RedisCache rwxCache;
@Resource
private Lock lock;
@Around("@annotation(xxxx.service.aspect.annotation.CacheFilter)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//Spel校验解析
String value = getAnnotation(pjp).value();
Assert.notNull(value, "value can not be null.");
String cacheKey = parseAnnotationValue(pjp, value);
String cache = rwxCache.get(cacheKey);
//缓存穿透拦截
if (CacheUtil.heldEmpty(cache)) {
LOGGER.info("[CacheFilterAspect] get empty from redis. key={}", cacheKey);
Class retCls = getReturnType(pjp);
if (null == retCls) return null;
return retCls.newInstance();
}
//无缓存处理
if (StringUtils.isBlank(cache)) {
LOGGER.info("[CacheFilterAspect] get null from redis. key={}", cacheKey);
//返回对象
Object result = null;
//防止缓存穿透
rwxCache.set(cacheKey, CacheUtil.EMPTY, 1, TimeUnit.HOURS);
//分布式锁
String cacheLock = CacheUtil.genLockKey(cacheKey);
if (lock.lock(cacheLock, 5000, TimeUnit.MILLISECONDS)) {
try {
//业务方法查询逻辑
result = pjp.proceed();
if (Objects.nonNull(result)) {
rwxCache.set(cacheKey, JSON.toJSONString(result), 1, TimeUnit.HOURS);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock(cacheLock);
}
} else {
//异步延时获取
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
Future<String> future = service.schedule(() -> rwxCache.get(cacheKey), 200, TimeUnit.MILLISECONDS);
try {
cache = future.get(500, TimeUnit.MILLISECONDS);
result = JSON.parseObject(cache, getReturnType(pjp));
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
//有效缓存处理
if (StringUtils.isNotBlank(cache)) {
LOGGER.info("[CacheFilterAspect] get data from redis. key={} , value={}", cacheKey, cache);
return JSON.parseObject(cache, getReturnType(pjp));
}
return pjp.proceed();
}
private static CacheFilter getAnnotation(ProceedingJoinPoint pjp) {
Annotation annotation = null;
try {
MethodSignature ms = (MethodSignature) pjp.getSignature();
annotation = pjp.getTarget()
.getClass()
.getDeclaredMethod(ms.getName(), ms.getParameterTypes())
.getAnnotation(CacheFilter.class);
} catch (Exception e) {
e.printStackTrace();
}
return (CacheFilter) annotation;
}
//match placeHolder using Regex and parse placeHolder using spel
private String parseAnnotationValue(ProceedingJoinPoint pjp, String value) {
String res = null;
//match placeholder like this "{xxx}"
Pattern p = Pattern.compile("(\\{.*?\\})");
Matcher m = p.matcher(value);
while (m.find()) {
String spel = placeHolder2SpEl(m.group());
String paramValue = parseSpel(getDeclaredMethod(pjp), pjp.getArgs(), spel);
res = value.replaceAll(Pattern.quote(m.group()), paramValue);
}
return res;
}
private Method getDeclaredMethod(ProceedingJoinPoint pjp) {
Method method = null;
try {
MethodSignature ms = (MethodSignature) pjp.getSignature();
method = pjp.getTarget()
.getClass()
.getDeclaredMethod(ms.getName(), ms.getParameterTypes());
} catch (NoSuchMethodException e) {
e.getMessage();
}
return method;
}
private Class getReturnType(ProceedingJoinPoint pjp) {
MethodSignature ms = (MethodSignature) pjp.getSignature();
return ms.getReturnType();
}
private String parseSpel(Method method, Object[] args, String spel) {
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int index = 0; index < params.length; index++) {
context.setVariable(params[index], args[index]);
}
Expression expression = parser.parseExpression(spel);
return String.valueOf(expression.getValue(context));
}
private String placeHolder2SpEl(String placeHolder) {
String placeHolderParam = CharMatcher.inRange('{', '}').removeFrom(placeHolder);
return Constants.Symbol.POUND.concat(placeHolderParam);
}
}
缓存移除
dao层api进行数据操作切面拦截,变更成功则remove cache key