• 自定义注解+AOP实现redis分布式锁


    最近项目中用到比较多的redis分布式锁

    每个方法都类似于这样

    String key = "";
    
    //尝试加锁
    if (! jedisManager.tryLock(key)) {
        throw new BizException("请稍后重试");
    }
    
    try {
    
        //do your biz
    
    }
    
    catch (Exception e) {
        throw e;
    }
    
    finally {
        //释放锁
        jedisManager.release(key);
    }
    

    非常的麻烦,而且每个人有每个人的写法。所以,决定将分布式锁与业务进行分离,便于我们以后后续开发

    我们需要定义一个分布式锁注解(RedisLock),分布式锁aop,分布式锁对象基类(LockDomian)

    RedisLock

    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface RedisLock {
    
    	RedisLocKeyEnum bizKey();
    	
    	/**
    	* 默认毫秒
    	* @return
    	*/
    	int expire() default 15000;
    	
    	/**
    	* 默认给前端的提示
    	* @return
    	*/
    	String errorMsg() default "请稍后重试";
    
    }
    

    LockDomian

    @Slf4j
    public class LockDomain {
    
        public static String KEY = "model = [%s], logKey = [%s] : [%s]";
    
        public String redisKey() {
            throw new BizException("请重写你的分布式锁对象的redisKey方法");
        }
    
        /**
         * 建议继承的类重写这个方法 方便日志查找
         * @return
         */
        public String logKey() {
            return String.valueOf(this.hashCode());
        }
    
        public void tryLockSucLog(MutexModelEnum model) {
    
            log.info(String.format(KEY, model.getCode(), this.logKey(), "获取锁成功"));
    
        }
    
        public void tryLockFaildLog(MutexModelEnum model) {
    
            log.info(String.format(KEY, model.getCode(), this.logKey(), "获取锁失败"));
    
        }
    
        public void releaseLog(MutexModelEnum model) {
    
            log.info(String.format(KEY, model.getCode(), this.logKey(), "释放锁成功"));
    
        }
    
        public void bizError(MutexModelEnum model) {
    
            log.info(String.format(KEY, model.getCode(), this.logKey(), "业务异常"));
    
        }
    
    }
    

    RedisLockAspect

    @Slf4j
    @Order(1)
    @Aspect
    @Component
    public class RedisLockAspect {
    
        @Autowired
        private JedisComponent jedisComponent;
    
        @Resource
        private Validator validator;
    
        @Around("@annotation(com.csy.core.aop.RedisLock)")
        public Result around(ProceedingJoinPoint joinPoint) {
    
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Method targetMethod = AopUtils.getMostSpecificMethod(method, joinPoint.getTarget().getClass());
    
            Object[] args = joinPoint.getArgs();
            for (Object arg : args) {
                String message = BeanValidators.validateWithErrorMessage(validator, arg);
                if (StringUtils.isNotBlank(message)) {
                    return Result.wrapErrorResult(message);
                }
            }
    
            RedisLock redisLock = AnnotationUtils.findAnnotation(targetMethod, RedisLock.class);
            if (redisLock == null) {
                return Data.wrapErrorResult("框架异常");
            }
            if (! (args[0] instanceof LockDomain)) {
                return Data.wrapErrorResult("请继承LockDomain");
            }
    
            LockDomain lockObj = (LockDomain) args[0];
            String key = redisLock.bizKey().getCode() + "_" + lockObj.redisKey();
    
            try {
    
                if (! jedisComponent.tryLock(key, redisLock.expire())) {
    
                    lockObj.tryLockFaildLog(redisLock.bizKey());
                    return Result.wrapErrorResult(redisLock.errorMsg());
                }
    
                lockObj.tryLockSucLog(redisLock.bizKey());
                return joinPoint.proceed();
            }
    
            catch (Throwable e) {
    
                lockObj.bizError(redisLock.bizKey());
    
                //参数异常捕获
                if (e instanceof ParamException) {
                    log.error("ParamException:", e);
                    ParamException paramException = (ParamException) e;
                    return Data.wrapErrorResult(paramException.getError().getErrorCode(), paramException.getError().getErrorMsg());
                }
    
                //自定义异常捕获
                if (e instanceof BizException) {
                    log.error("BizException", e);
                    BizException bizException = (BizException) e;
                    return Data.wrapErrorResult(bizException.getError().getErrorCode(), bizException.getError().getErrorMsg());
                }
    
                log.error("系统异常:", e);
    
                return Result.wrapErrorResult(ErrorCode.SERVER_ERROR);
            }
    
            finally {
    
                lockObj.releaseLog(redisLock.bizKey());
                jedisComponent.delKey(key);
    
            }
        }
    
    }
    

    分布式锁业务实现

    public class RedisLockDemo {
    
        @RedisLock(bizKey = MutexModelEnum.TEST)
        public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
    
            //do your biz
    
            return Result.success(true);
        }
    
        @Getter
        @Setter
        public class RedisLockTestRequest extends LockDomain {
    
            private Long userId;
    
            private String userName;
    
            /**
             * 以userId作为分布式锁的key
             * @return
             */
            @Override
            public String redisKey() {
                return String.valueOf(this.userId);
            }
    
            @Override
            public String logKey() {
                return String.valueOf(this.userId);
            }
            
        }
    }
    

    可以看到。我们只要在方法上加上@RedisLock,指定锁的Model,再对入参继承LockDomain,指定redisKey和logKey就行。

    注意事项

    aop相关问题

    有的同学可能想降低锁的粒度或者单纯的想抽出一个方法。比如:

    public void a(RedisLockTestRequest request) {
        this.RedisLockTest(request);
    }
    
    @RedisLock(bizKey = MutexModelEnum.TEST)
    public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
    
        //do your biz
    
        return Result.success(true);
    }
    

    这种情况下,外部调用a方法,aop是不起作用的

    因为aop是运行时织入,获取调用的目标方法(也就是a),判断是否是切点,再匹配切面。内部方法(不是目标方法)不进行织入。

    如何解决上述情况?
    我们使用AopContext

    public void a(RedisLockTestRequest request) {
        RedisLockDemo currentProxy = (RedisLockDemo) AopContext.currentProxy();
        currentProxy.RedisLockTest(request);
    }
    
    @RedisLock(bizKey = MutexModelEnum.TEST)
    public Result<Boolean> RedisLockTest(RedisLockTestRequest request) {
    
        //do your biz
    
        return Result.success(true);
    }
    

    通过AopContext获取到当前的代理类,然后调用。

    上述涉及到的aop原理我会后续出个专门的aop浅析。

    分布式锁相关问题

    可能网上有一些分布式锁它是先setnx 然后 expire。其实是有问题的。因为它不是一个原子操作。
    我们应该使用Jedis类下的set方法。一步设置值和过期时间

    public String set(final String key, final String value, final String nxxx, final String expx, final long time) {
        checkIsInMulti();
        client.set(key, value, nxxx, expx, time);
        return client.getStatusCodeReply();
    }
  • 相关阅读:
    mydumper/myloader使用详解
    myloader原理介绍
    mydumper原理介绍
    mydumper安装
    sysbench压测mysql基本步骤
    sysbench 0.4.12安装
    MySQL5.7多源复制
    PXC5.7集群部署
    destoon8.0生成输出热门搜索sitemap地图方法
    百度+搜狗快排程序核心代码分享
  • 原文地址:https://www.cnblogs.com/chenshengyue/p/10825162.html
Copyright © 2020-2023  润新知