基于Redission的分布式锁

发布时间 2023-08-23 17:43:46作者: zhangliangddq

分布式锁的设计共分为3步

  1. 定义注解
  2. 对注解进行扫描
  3. 使用注解

加锁核心逻辑为
RLock rLock = redissonClient.getLock(key);
//是否加锁成功
boolean isLock = rLock.tryLock(timeOut, expireTime, timeUnit);

1.定义注解 LockAction

package com.jwds.app.compont.cache.annotation;

import com.jwds.app.compont.cache.service.lock.AppLockService;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

/**
 * 同步锁注解
 *
 * @author by liang.zhang
 * @Description
 * @Date 2021/9/7
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface LockAction {

  /**
   * 锁的资源,key。支持spring El表达式
   * 例如:#user.id
   */
  String value() default "'default'";

  /**
   * 等待时间 在等待时间内将会一直等待
   * 默认-1  不进行等待,获取失败直接返回结果
   * @return
   */
  long timeOut() default AppLockService.TIME_OUT;

  /**
   * 获取锁的等待时间单位 超过这个时间就获取失败
   *
   * @return
   */
  TimeUnit timeUnit() default TimeUnit.MILLISECONDS;

  /**
   * 过期时间 默认30分钟,过期后自动删除key  -1永远不过期
   *
   * @return
   */
  long expireTime() default AppLockService.EXPIRE_TIME;

  /**
   * 获取锁失败时的重试次数 默认为0
   * 当获取锁失败时,会进行锁的重新获取
   */
  int retryTimes() default AppLockService.RETRY_TIMES;

  /**
   * 获取锁失败时的重试的间隔时间 单位毫秒
   */
  long sleepMills() default AppLockService.RETRY_SLEEP_MILLIS;

  /**
   * 加锁失败的回调方法 参数为方法的入参
   * 回调进入该方法,
   * @return
   */
  String fallbackMethod() default "";

  /**
   * 未拿到锁 提示消息
   *
   * @return
   */
  String errorMsg() default "操作过于频繁,请稍后再试!";
}

2. 对注解进行切面处理

//主要注意加锁失败后的回调方法

@Around("lockPoint()")
  public Object around(ProceedingJoinPoint pjp) throws Throwable {

    MethodSignature signature = (MethodSignature) pjp.getSignature();
    Method method = signature.getMethod();
    LockAction lockAction = method.getAnnotation(LockAction.class);
    String key = lockAction.value();
    Object[] args = pjp.getArgs();
    //系统编码+key 组成唯一key
    //NameSpace命名空间
    key = "RedisLock" + AppConstant.COLON + parse(key, method, args);

    //重试次数
    int retryTimes = lockAction.retryTimes();
    boolean lock = lockService
        .lock(key, lockAction.timeOut(), lockAction.expireTime(), lockAction.timeUnit(), retryTimes,
            lockAction.sleepMills());
    //加锁失败抛出异常 如果存在回调方法则不抛出异常,由业务自己去处理
    if (!lock) {
      //回调方法存在的时候,调用
      if (null != lockAction.fallbackMethod() && !lockAction.fallbackMethod().trim().equals("")) {
        log.error("get lock failed : {}进入失败回调方法", key);
        //获取方法所在的类(controller)
        Class<?> beanType = signature.getDeclaringType();
        Object object = beanType.newInstance();

        //参数转变
        Class<?>[] params = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
          Object o = args[i];
          params[i] = o.getClass();
        }
        try {
          //回调失败方法
          Method infoMethod = beanType.getMethod(lockAction.fallbackMethod(), params);
          if (infoMethod==null){
            throw new RuntimeException("回调方法:"+lockAction.fallbackMethod()+"不存在或为private的");
          }
          infoMethod.invoke(object, args);
        } catch (Exception e) {
          log.error("调用加锁失败回调方法{}异常", lockAction.fallbackMethod(), e);
          throw new LockException(lockAction.errorMsg());
        }
      } else {
        log.error("get lock failed : {}没有失败回调方法", key);
        throw new LockException(lockAction.errorMsg());
      }
    }

 //具体的锁代码
 @Override
  public boolean lock(String key, long timeOut, long expireTime, TimeUnit timeUnit, int retryTimes,
      long retrySleepMillis) {
    //增加系统编码
    key = appCacheProperties.getSysCode() + AppConstant.COLON + key;

    boolean result = setRedis(key, timeOut, expireTime, timeUnit);
    // 如果获取锁失败,按照传入的重试次数进行重试
    while ((!result) && retryTimes-- > 0) {
      try {
        log.debug("lock failed, retrying..." + retryTimes);
        Thread.sleep(retrySleepMillis);
      } catch (InterruptedException e) {
        return false;
      }
      result = setRedis(key, timeOut, expireTime, timeUnit);
    }
    return result;
  }
  
 //加锁的核心代码
  /**
   * @param key
   * @param timeOut    超时时间
   * @param expireTime 过期时间
   * @param timeUnit   单位
   * @return
   */
  private boolean setRedis(String key, long timeOut, long expireTime, TimeUnit timeUnit) {
    RLock rLock = redissonClient.getLock(key);
    log.debug("原始传入上锁时间:" + timeOut);
    //是否加锁成功
    boolean isLock = false;
    try {
      //尝试加锁
      isLock = rLock.tryLock(timeOut, expireTime, timeUnit);
    } catch (Exception e) {
      isLock = false;
      log.error("===>{}操作Redis失败:{}", key, e.getMessage());
      throw new RuntimeException(e.getMessage());
    }
    log.debug("取得加锁状态:" + isLock + ",最终超时间:" + timeOut);
    return isLock;
  }

3.注解使用

其中value 支持EL表达式
@LockAction(value = "#reqDto.queryCondition.id", fallbackMethod = "fallbackMethod")

也可以手动调用代码 进行加锁和解锁操作
AppLockService.lock();