zoukankan      html  css  js  c++  java
  • 基于注解的方法缓存

    基于注解的方法缓存

    在大数据项目中, 查询impala或hive非常耗时耗资源, 所以, 对于同一方法相同参数的请求, 应该保存到缓存中, 而不是每次都去查数据库, 方法的返回值可以保存到Mongodb或redis中

    具体实现:

    @MethodCache注解

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface MethodCache {
    
        /**
         * 指定缓存的过期时间, 默认60秒
         *
         * @return int
         */
        int expireSeconds() default 60;
    
        /**
         * 缓存的key, 如果不指定, 默认按照方法的签名作为key
         *
         * @return String
         */
        String key() default "";
    
        /**
         * 缓存防击穿的标志, 默认是开启防击穿功能
         *
         * @return boolean
         */
        boolean limitQuery() default true;
    
        /**
         * 防击穿的时限
         *
         * @return int
         */
        int limitQuerySeconds() default 5;
    
        /**
         * 是否保存空的结果
         *
         * @return boolean
         */
        boolean saveEmptyResult() default true;
    
    }
    

    MethodCacheAspect切面

    该切面的核心思想就是用方法的签名+实参对象的哈希值作为key, 先从缓存中取, 取不到再调用具体方法去查询, 查询结果后保存到缓存中, 其中可以设置一次只能有一个线程去查, 还可以设置重试次数, 还可以设置是否保存空结果

    @Aspect
    @Order(value = 2)
    @Component
    public class MethodCacheAspect {
    
        @Resource
        private JedisPool jedisPool;
    
        @Resource
        private DisLockUtil disLockUtil;
    
        private static final Logger LOGGER = LoggerFactory.getLogger(MethodCacheAspect.class);
    
        private static final String EMPTY_RESULT = "NULL";
    
        /**
         * 切面具体的操作
         *
         * @param proceedingJoinPoint 切面
         * @param methodCache         注解
         * @return Object
         * @throws Throwable 抛出异常
         */
        @Around("@annotation(methodCache)")
        public Object execute(ProceedingJoinPoint proceedingJoinPoint, MethodCache methodCache) throws Throwable {
            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = methodSignature.getMethod();
    
            // 方法参数解析
            int size = proceedingJoinPoint.getArgs().length;
            // 解析请求参数
            List<Object> list = parseRequestParam(proceedingJoinPoint, size);
            // 根据方法获取相应的key
            String key = methodCache.key();
            if (StringUtils.isBlank(key)) {
                key = getSignature(method);
            }
            key += HashAlgorithms.mixHash(JSON.toJSONString(list));
            Object deserialize = tryGetFromCache(key);
            if (deserialize != null) {
                // 防止缓存击穿, 查询不到数据时也会设置空结果的标记, 避免直接把压力落到DB上
                if (EMPTY_RESULT.equals(deserialize)) {
                    return null;
                }
                return deserialize;
            }
            Object proceed;
            String mutexKey = "mutex_" + key;
            if (methodCache.limitQuery()) {
                boolean lock = disLockUtil.lock(mutexKey, methodCache.limitQuerySeconds());
                // 如果第一次设置分布式锁失败, 最多允许重试三次
                int count = 1;
                while (!lock && count < 3) {
                    lock = disLockUtil.lock(mutexKey, methodCache.limitQuerySeconds());
                    count++;
                    TimeUnit.SECONDS.sleep(1);
                }
                if (lock) {
                    // 允许查询
                    proceed = executeConcreteMethod(proceedingJoinPoint, mutexKey);
                    Object cacheResult = proceed;
                    // 缓存中不存在, 需要执行方法查询
                    if (cacheResult == null) {
                        cacheResult = EMPTY_RESULT;
                    }
                    try (Jedis jedis = jedisPool.getResource()) {
                        jedis.setnx(key.getBytes(), KryoUtil.writeToByteArray(cacheResult));
                        jedis.expire(key, methodCache.expireSeconds());
                    }
                } else {
                    LOGGER.warn("设置防击穿锁失败, key为:{}", mutexKey);
                    throw new CustomException(ErrorCodeEnum.DUPLICATE_REQUEST.getMessage());
                }
            } else {
                // 允许查询
                proceed = executeConcreteMethod(proceedingJoinPoint, mutexKey);
            }
            return proceed;
        }
    
        /**
         * 执行具体的方法
         *
         * @param proceedingJoinPoint 切面
         * @return Object
         * @throws Throwable 异常
         */
        private Object executeConcreteMethod(ProceedingJoinPoint proceedingJoinPoint, String mutexKey) throws Throwable {
            Object proceed;
            try {
                proceed = proceedingJoinPoint.proceed();
            } finally {
                disLockUtil.unlock(mutexKey, false);
            }
            return proceed;
        }
    
        /**
         * 尝试从缓存中获取
         *
         * @param key key
         * @return Object
         */
        private Object tryGetFromCache(String key) {
            byte[] resultBytes;
            try (Jedis jedis = jedisPool.getResource()) {
                if (!jedis.exists(key)) {
                    return null;
                }
                resultBytes = jedis.get(key.getBytes());
            }
            if (resultBytes != null) {
                LOGGER.info("key:{}获取到缓存", key);
                return KryoUtil.readFromByteArray(resultBytes);
            }
            return null;
        }
    
        /**
         * 解析请求参数
         *
         * @param proceedingJoinPoint 切面
         * @param size 参数个数
         * @return List<Object>
         */
        private List<Object> parseRequestParam(ProceedingJoinPoint proceedingJoinPoint, int size) {
            Object[] args = proceedingJoinPoint.getArgs();
            List<Object> argList = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                if (args[i] instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest) args[i];
                    argList.add(request.getParameterMap());
                } else if (args[i] instanceof HttpServletResponse || args[i] instanceof HttpSession
                        || args[i] instanceof HttpCookie) {
                    continue;
                } else {
                    argList.add(args[i]);
                }
            }
            return argList;
        }
    
        /**
         * 生成方法签名
         *
         * @param method 方法
         * @return String
         */
        private String getSignature(Method method) {
            StringBuilder sb = new StringBuilder();
            String methodName = method.getName();
            if (StringUtils.isNotBlank(methodName)) {
                sb.append(method).append("#");
            }
            return sb.toString();
        }
    
    }
    

    项目已经上传到gitee和github

    gitee: https://gitee.com/ericwo/second-kill

    github: https://github.com/wangjisong1993/second-kill

  • 相关阅读:
    vue 部署到服务器
    半小时学会 Vuex 数据共享
    Vue 第一次安装 经历 vue cli 3.0
    第一次使用视频截图 ant design
    Luckysheet
    关于导出--分页
    ADO.net很重要
    委托到底是什么? (转载)
    生成流水单号
    Ext.NET 基础学习笔记07 (GridPanel用法)
  • 原文地址:https://www.cnblogs.com/shanzhai/p/10541920.html
Copyright © 2011-2022 走看看