分布式-技术专区-Redis分布式锁实现-第二步

再上次篇章中汇集了相关的分布式锁的概念进行控制,接下来我们采用的是注解声明式开发服务方案,进行声明式开发代替编程式开发方案。 
1.利用aop实现分布式锁
2.只用在方法上加个注解,同时加上了重试机制

1.前提我们可以个性化定制一些注解例如:RedisLock注解操作

@RedisLock(lockPrefix = AbstractRedisContants.DIST_LOCK_FUND, lockParameter = "fundId")
public void handle(FundAmountOptTypeEnum optType, Long fundId, BigDecimal amount, String operator, String remark) {
  strategyMap.get(optType).handle(fundId, amount, operator, remark);
}
 

2.定义RedisLock注解开发服务

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.Order;

/**
* redis锁注解
* @date 2018/12/27
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
@Order(value = 10)
public @interface RedisLock {
  // 锁前缀
  String lockPrefix() default "";

  // 方法参数名(用于取参数名的值与锁前缀拼接成锁名),尽量不要用对象map等,对象会toString后与锁前缀拼接
  String lockParameter() default "";

  // 尝试加锁,最多等待时间(毫秒)
  long lockWait() default 3000L;

  // 自动解锁时间 (毫秒)
  long autoUnlockTime() default 10000L;

  // 重试次数
  int retryNum() default 0;

  // 重试等待时间 (毫秒)
  long retryWait() default 500L;

}

  @Order(value = 10)
  当使用了@Transactional 或其它切面时,相当于在执行执行多次次AOP切面。那么我们需要通过order 属性去定义AOP切面的先后执行顺序。 order越小,在AOP的chain 中越靠前,越先执行。(chain模式)

 3.RedisLockAspect

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import com.feitai.jieya.server.common.annotation.RedisLock;
import com.feitai.jieya.server.common.exception.BusinessException;
import com.feitai.jieya.server.utils.StringUtils;

import lombok.extern.slf4j.Slf4j;

/**
* Description: 分布式锁
* <p>
* 先获取锁, 获取不到则继续等待(指定时间), 失败次数(指定)次后跳出, 消费降级(抛出,系统繁忙稍后再试) 如果没有重试次数,方法返回null 记得捕获NP 当重试次数有, 但是重试间隔时间没写, 默认200ms 间隔
* </p>
*/
@Aspect
@Component
@Slf4j
@Order(10)
public class RedisLockAspect {

private static final String LOCK_NAME = "lockName";
private static final String lOCK_WAIT = "lockWait";
private static final String AUTO_UNLOCK_TIME = "autoUnlockTime";
private static final String RETRY_NUM = "retryNum";
private static final String RETRY_WAIT = "retryWait";

/**
* redis工具类
*/
@Autowired
private RedissonClient redissonClient;

@Pointcut("@annotation(com.feitai.jieya.server.common.annotation.RedisLock)")
public void lockAspect() {}

@Around("lockAspect()")
public Object lockAroundAction(ProceedingJoinPoint proceeding) throws Throwable {

  // 获取注解中的参数
  Map<String, Object> annotationArgs = this.getAnnotationArgs(proceeding);
  String lockName = (String)annotationArgs.get(LOCK_NAME);
  Assert.notNull(lockName, "分布式,锁名不能为空");
  int retryNum = (int)annotationArgs.get(RETRY_NUM);
  long retryWait = (long)annotationArgs.get(RETRY_WAIT);
  long lockWait = (long)annotationArgs.get(lOCK_WAIT);
  long autoUnlockTime = (long)annotationArgs.get(AUTO_UNLOCK_TIME);

  // 获取锁
  RLock lock = redissonClient.getLock(lockName);
  try {
    boolean res = lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS);
  if (res) {
    // 执行主逻辑
    return proceeding.proceed();

  } else {
    // 如果重试次数为零, 则不重试
  if (retryNum <= 0) {
    log.info(String.format("{%s}已经被锁, 不重试", lockName));
    throw new BusinessException(String.format("{%s}已经被锁, 不重试", lockName));
  }

  if (retryWait == 0) {
    retryWait = 200L;
  }
  // 设置失败次数计数器, 当到达指定次数时, 返回失败
  int failCount = 1;
    while (failCount <= retryNum) {
    // 等待指定时间ms
    Thread.sleep(retryWait);
    if (lock.tryLock(lockWait, autoUnlockTime, TimeUnit.SECONDS)) {
    // 执行主逻辑
    return proceeding.proceed();
  } else {
    log.info(String.format("{%s}已经被锁, 正在重试[ %s/%s ],重试间隔{%s}毫秒", lockName, failCount, retryNum,retryWait));
    failCount++;
  }
  }
  throw new BusinessException("系统繁忙, 请稍等再试");
  }
  } catch (Throwable throwable) {
    log.error(String.format("执行分布式锁发生异常锁名:{%s},异常名称:{%s}", lockName, throwable.getMessage()));
    throw throwable;
  } finally {
    lock.unlock();
  }
}

/**
* 获取锁参数
*
* @param proceeding
* @return
*/
private Map<String, Object> getAnnotationArgs(ProceedingJoinPoint proceeding) {
// if (!(objs[i] instanceof ExtendedServletRequestDataBinder)
// && !(objs[i] instanceof HttpServletResponseWrapper)) {

  proceeding.getArgs();
  Object[] objs = proceeding.getArgs();
  String[] argNames = ((MethodSignature)proceeding.getSignature()).getParameterNames(); // 参数名

  Class target = proceeding.getTarget().getClass();
  Method[] methods = target.getMethods();
  String methodName = proceeding.getSignature().getName();
  for (Method method : methods) {
    if (method.getName().equals(methodName)) {
      Map<String, Object> result = new HashMap<String, Object>();
      RedisLock redisLock = method.getAnnotation(RedisLock.class);

      if (StringUtils.isNotBlank(redisLock.lockParameter())) {
        for (int i = 0; i < objs.length; i++) {
          if (redisLock.lockParameter().equals(argNames[i])) {
            result.put(LOCK_NAME, redisLock.lockPrefix() + objs[i]);
            break;
          }

        }
      } else {
        result.put(LOCK_NAME, redisLock.lockPrefix());
      }
        result.put(lOCK_WAIT, redisLock.lockWait());
        result.put(AUTO_UNLOCK_TIME, redisLock.autoUnlockTime());
        result.put(RETRY_NUM, redisLock.retryNum());
        result.put(RETRY_WAIT, redisLock.retryWait());

        return result;
      }
     }
     throw new RuntimeException("异常");

  }

posted @ 2019-11-27 19:53  洛神灬殇  阅读(232)  评论(0编辑  收藏  举报