自定義注解+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();
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM