引言:

非单点应用,JDK自带的管程锁(即:监视器锁、Monitor锁,通过synchronized关键字来实现加锁)、或可重入锁(ReentrantLock)已无法做到对临界资源的加锁,达到同步访问的目的。

简介:

分布式锁基于Redis + SpringAOP来实现,通过申明式注解的方式为具体临界资源加锁,达到同步访问的目的。

实现:

RedissionLock注解类定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedissonLock {

    /**
     * 锁失效时间
     * 默认-1秒,永久持有,直到当前锁被释放
     */
    int expireTime() default -1;

    /**
     * 抢锁等待时间,默认30秒,-1表示不等待,成功或失败都立即返回
     */
    int waitTime() default 30;

    /**
     * 锁过期时间单位,默认秒
     */
    TimeUnit expireTimeUnit() default TimeUnit.SECONDS;

    /**
     * 抢锁等待时间单位,默认秒
     */
    TimeUnit waitTimeUnit() default TimeUnit.SECONDS;

    //--------------------------------------------------------------------
    // redisKey 对 values、expressions、spEL、keyGenerator 进行拼接
    //--------------------------------------------------------------------

    /**
     * redisKey
     */
    String[] values() default {};

    /**
     * redisKey
     * 支持OGNL表达式
     */
    String[] expressions() default {};

    /**
     * redisKey
     * Spring Expression Language (SpEL) expression for computing the key dynamically.
     */
    String[] spEL() default {};


    /**
     * The bean name of the custom {@link RedisKeyGenerator} to use.
     */
    String keyGenerator() default "";


    /**
     * 未拿锁时的错误码
     * MSG_1000007(1000007, "系统繁忙,请稍后再试(key=%s)")
     */
    ErrCode errorCode() default ErrCode.MSG_1000007;

    /**
     * 等锁超时,是否抛出异常
     */
    boolean throwExceptionWhenTryLockTimeOut() default true;


}

RedisKeyGenerator接口定义:

@FunctionalInterface
public interface RedisKeyGenerator {

   /**
    * Generate a key for the given method and its parameters.
    * @param target the target instance
    * @param method the method being called
    * @param params the method parameters (with any var-args expanded)
    * @return a generated key
    */
   String generate(Object target, Method method, Object... params);

}

切面定义:

/**
 * 确保此切面执行顺序在事务切面之前。事务切面执行顺序是 {@link Integer#MAX_VALUE}
 */
@Slf4j
@Aspect
@Order(0)
@Component
public class LockAop implements ApplicationContextAware {

    @Resource
    private RedissonClient redissonClient;

    @Pointcut("@annotation(RedissonLock)")
    private void pointCut() {

    }

    private static final String prefix = "c:e:lock:";

    @Around(value = "pointCut() && @annotation(redissonLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {

        String redisKey;

        // 字符串
        StringJoiner joiner = new StringJoiner(":", prefix, StringUtils.EMPTY);
        String[] values = redissonLock.values();
        if (ArrayUtils.isNotEmpty(values)) {
            for (String value : values) {
                joiner.add(value);
            }
        }

        // OGNL 表达式
        String[] expressions = redissonLock.expressions();
        if (ArrayUtils.isNotEmpty(expressions)) {
            // 根对象
            Map<String, Object> rootObject = getVariables(joinPoint);
            for (String expression : expressions) {
                joiner.add(String.valueOf(OgnlCache.getValue(expression, rootObject)));
            }
        }

        // Spring EL 表达式
        String[] spEL = redissonLock.spEL();
        if (ArrayUtils.isNotEmpty(spEL)) {
            StandardEvaluationContext context = new StandardEvaluationContext();

            // 上下文对象中的变量
            Map<String, Object> rootObject = getVariables(joinPoint);
            context.setVariables(rootObject);

            SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
            for (String el : spEL) {
                Expression expression = spelExpressionParser.parseExpression(el);
                String value = expression.getValue(context, String.class);
                joiner.add(value);
            }
        }

        // key 生成器
        String keyGenerator = redissonLock.keyGenerator();
        if (StringUtils.isNotEmpty(keyGenerator)) {
            if (applicationContext.containsBean(keyGenerator)) {
                RedisKeyGenerator bean = applicationContext.getBean(RedisKeyGenerator.class, keyGenerator);
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                String value = bean.generate(joinPoint.getTarget(), methodSignature.getMethod(), joinPoint.getArgs());
                joiner.add(value);
            }
        }

        redisKey = joiner.toString();

        if (StringUtils.isEmpty(redisKey)) {
            // MSG_1000008(1000008, "锁key不能为空")
            throw new ServiceException(ErrCode.MSG_1000008);
        }

        // 尝试获取锁
        RLock rLock = redissonClient.getLock(redisKey);

        // 统一过期时间和等待时间单位为纳秒
        long expireTime = redissonLock.expireTime();
        long waitTime = redissonLock.waitTime();
        if (expireTime != -1) {
            expireTime = redissonLock.expireTimeUnit().toNanos(expireTime);
        }
        if (waitTime != -1) {
            waitTime = redissonLock.waitTimeUnit().toNanos(waitTime);
        }

        boolean tryLock = rLock.tryLock(waitTime, expireTime, TimeUnit.NANOSECONDS);
        if (!tryLock) {
            log.info("key=[{}] get the lock failure", redisKey);
            throw ServiceException.getInstance(redissonLock.errorCode(), redisKey);
        }
        log.info("key=[{}] get the lock success", redisKey);

        Object proceed;
        try {

            proceed = joinPoint.proceed();

        } finally {
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
                log.info("key=[{}] release the lock success", redisKey);
            }
        }

        return proceed;
    }

    private static Map<String, Object> getVariables(ProceedingJoinPoint joinPoint) {
        // 切面方法参数
        Object[] args = joinPoint.getArgs();
        // 拿到方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Map<String, Object> map = new HashMap<>();
        // 把参数名和参数对象仍入集合
        for (int i = 0; i < methodSignature.getParameterNames().length; i++) {
            String parameterName = methodSignature.getParameterNames()[i];
            Object arg = args[i];
            map.put(parameterName, arg);
        }
        return map;
    }

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

OGNL表达式使用参考:

所有请求参数会用参数名和参数值存入一个Map中,并设置为根对象。

@RedissonLock(expressions = {
                "req.activityId", 
                "addReq.dayBound.countBound", 
                "list1[0].activityStartTime",
                "list2[0]", 
                "name", 
                "map.v555", 
                "arr[0].activityId"})
public void lockTest(ActivityTerminatedRequest req, 
                     ActivityAddAndUpdateRequest addReq, 
                     List<ActivityAddAndUpdateRequest> list1,
                     List<Integer> list2, String name, 
                     Map<String, String> map, 
                     ActivityAddAndUpdateRequest[] arr) {

}

SPEL表达式使用参考:

所有请求参数会被设置成上下文变量。

@RedissonLock(
        spEL = {"#req.activityId", 
                "#addReq.dayBound.countBound", 
                "#list1[0].activityStartTime",
                "#list2[0]", 
                "#name", 
                "#map['v555']", 
                "#arr[0].activityId"})

public void lockTest(ActivityTerminatedRequest req, 
                     ActivityAddAndUpdateRequest addReq, 
                     List<ActivityAddAndUpdateRequest> list1,
                     List<Integer> list2, String name, 
                     Map<String, String> map, 
                     ActivityAddAndUpdateRequest[] arr) {
}

完整使用参考:

@RedissonLock(waitTime = 10, 
        values = {PromoDomain.PROMO_DOMAIN_NAME, PromoDomain.ACTIVITY_DOMAIN_NAME},
        expressions = {"req.activityId", "addReq.dayBound.countBound", 
                "list1[0].activityStartTime",
                "list2[0]", "name", "map.v555", "arr[0].activityId"},
        spEL = {"#req.activityId", "#addReq.dayBound.countBound",
                 "#list1[0].activityStartTime",
                "#list2[0]", "#name", "#map['v555']", "#arr[0].activityId"},
        keyGenerator = "activityKeyGenerator")
@Override
public void lockTest(ActivityTerminatedRequest req, 
                        ActivityAddAndUpdateRequest addReq, 
                        List<ActivityAddAndUpdateRequest> list1,
                        List<Integer> list2, 
                        String name, 
                        Map<String, String> map, 
                        ActivityAddAndUpdateRequest[] arr) {


}


@Service(value = "activityKeyGenerator")
public class ActivityKeyGenerator implements RedisKeyGenerator {
    @Override
    public String generate(Object target, Method method, Object... params) {

        String name = (String)params[4];

        return name + "#Generate";
    }
}

此锁只适宜部分场景,红锁,点锁不提供支持。锁重入时,请注意key的"一致性"。
Note:key的定义必须以领域名称和聚合名称打头,防止key重复。

引用:

https://commons.apache.org/proper/commons-ognl/language-guide...
https://docs.spring.io/spring-framework/docs/3.2.x/spring-fra...


NewBie
10 声望0 粉丝