注解的方式实现分布式锁

  • Post author:
  • Post category:其他


前言

在日常开发中,为了防止高并发,在不依赖过多的中间件的情况下,最常使用的分布式锁之一是 Redis锁。使用Redis锁就不得不面临一个问题,就是在业务代码中要控制Redis加锁、释放锁等等,对代码的侵入性较强。本文采用注解的方式为方法体增加分布式锁,唯一标识从方法参数中动态获取。

优点


  1. 无侵入

    。通过注解实现加锁和释放锁,代码中只需关注业务实现,无须关心“锁”问题,避免代码侵入。

  2. 无死锁

    。即使某一线程中断没能释放锁,在到达指定的时间后,程序会自动释放锁。

  3. 锁唯一独有

    。加锁和释放锁必须由同一线程执行,不会出现A线程加锁后,B线程将锁释放。

  4. 支持多种方式传参做key

    。通过注解指定参数名,通过反射,动态获得key。

  5. 线程间锁互斥

    。在同一时间内,仅有一个线程持有锁,避免多个线程同时执行逻辑,出现并发情况。

代码解释

  1. 通过AOP切面对方法执行前和方法执行后加锁和释放锁
  2. 注解参数传入key保证唯一,通过key加锁保证次key不会重复
  3. 默认释放锁时间30分钟,可以通过注解参数设置不同方法的默认释放锁时间防止死锁

上代码

分布式锁注解,适用于方法 通过key定义请求唯一值,防止业务重复提交, 业务重复提交判断需要在方法内判断,注解能拦截正在提交中不能再次请求唯一值。 key中禁止使用 ” p # + . “字符 这几个字符作为解析key的关键字

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface LocalLock {

    /**请求唯一标识*/
    String key() default "";

    /**默认30分钟*/
    long expireTime() default 1800L;

    /**redis保存的值*/
    String value() default "";
}
复制代码

AOP切面对注解的方法加锁和释放锁

@Resource
private RedissonClient redissonClient;

@Resource
private RedisLockUtil redisUtil;


@Pointcut("@annotation(com.cdgas.hallsite.config.lock.LocalLock)")
public void annotationPointcut() {
}

@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    // 获得当前访问的class
    Class<?> className = joinPoint.getTarget().getClass();
    // 获得访问的方法名
    String methodName = joinPoint.getSignature().getName();
    // 得到方法的参数的类型
    Class<?>[] argClass = ((MethodSignature) joinPoint.getSignature()).getParameterTypes();
    Object[] args = joinPoint.getArgs();
    String key = "";
    long expireTime = 1800L;
    String value = "local_lock";
    //是否执行锁操作
    boolean lock = true;
    try {
        // 得到访问的方法对象
        Method method = className.getMethod(methodName, argClass);
        method.setAccessible(true);
        // 判断是否存在@LocalLock注解
        if (method.isAnnotationPresent(LocalLock.class)) {
            LocalLock annotation = method.getAnnotation(LocalLock.class);
            key = getRedisKey(args, annotation);
            expireTime = annotation.expireTime();
            if(StrUtil.isNotBlank(annotation.value())){
                value = annotation.value();
            }
        }
    } catch (Exception e) {
        log.info("分布式锁方法注解生成key失败,方法不执行分布式锁。错误信息:{}", ExceptionUtil.getMsg(e));
        lock = false;
    }
    key = methodName+":local_lock:" + key;

    //返回结果
    RLock redissonLock = redissonClient.getLock(key);
    try {
        //分布式锁
        if (lock && StrUtil.isNotBlank(key)) {
            //加锁 操作很类似Java的ReentrantLock机制
            redissonLock.lock();
            final boolean locker = redisUtil.lock(key, value);
            if (!locker) {
                throw new AppException("请求频繁");
            }
        }
        Object res = joinPoint.proceed();
        return res;
    } catch (Exception e) {
        log.info("分布式锁中方法执行错误释放锁,错误信息:{}", ExceptionUtil.getMsg(e));
        throw e;
    } finally {
        //释放锁
        redissonLock.unlock();
        redisUtil.unlock(key, value);
    }
}
复制代码

解析key方法

/**
 * 解析key对应的参数
 *
 * @param args
 * @param annotation
 * @return
 * @throws Exception
 */
private String getRedisKey(Object[] args, LocalLock annotation) throws Exception {
    String primalKey = annotation.key();
    String redisKey = "";
    String[] splitAdd = primalKey.split("\+");
    for (int i = 0; i < splitAdd.length; i++) {
        String param =  splitAdd[i];
        String[] split = param.split("#p");
        for (int j = 0; j < split.length; j++) {
            String p = split[j];
            if(StrUtil.isBlank(p)){
                continue;
            }
            //判断是直接获取,还是获取对象中的参数
            if(p.contains(".")){
                String[] splitField = p.split("\.");
                int idx = Integer.parseInt(splitField[0]);
                String field = splitField[1];
                Object parValueByObject = getFieldValueByObject(args[idx], field);
                redisKey += parValueByObject;
            }else {
                if(StrUtil.isLowerCase(p)){
                    redisKey += p;
                }else {
                    int idx = Integer.parseInt(p);
                    redisKey += args[idx];
                }
            }
        }
    }
    log.info("分布式锁注解获取到的key值:{}",redisKey);
    return redisKey;
}

/**
 * 获取对象中指定字段的值
 *
 * @param obj
 * @param field
 * @return
 * @throws Exception
 */
private Object getFieldValueByObject(Object obj, String field) throws Exception {
    Class cls = obj.getClass();
    Method method = cls.getMethod("get" + StrUtil.upperFirst(field));
    method.setAccessible(true);
    log.info(method.getName());
    return method.invoke(obj);
}
复制代码

测试

@LocalLock("#p0+#p1.name+#p1.gender")
public Result<String> test(Long flag, Order order) {
    
}