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

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

缓存问题

优化解决三个问题:

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

缓存过滤

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大摩羯先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值