[调优]缓存处理逻辑优化

[调优]缓存处理逻辑优化

缓存问题

优化解决三个问题:

  • 缓存逻辑抽离业务逻辑,以缓存切面形式进行拦截和过滤
  • 缓存穿透、缓存击穿
  • 缓存与数据库的数据一致性

缓存过滤

  • ① 通过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

posted @ 2021-02-22 14:27  大摩羯先生  阅读(28)  评论(0编辑  收藏  举报