引言:

非单点利用,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接口定义:

@FunctionalInterfacepublic 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)@Componentpublic 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")@Overridepublic 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...