redis 分布式锁处理接口幂等性

                 之前博文中介绍过token 机制处理 接口幂等性问题,这种方式一个问题对代码的入侵比较多,

相对书写代码来讲就比较麻烦,本文介绍使用 redis 分布式锁机制解决接口幂等性问题。

1:定义注解:

 

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

    /**
     * 设置请求锁定时间,超时后自动释放锁
     *
     * @return
     */
    int lockTime() default 10;

}

 

2:AOP 实现 注解 @Ide 的拦截处理

 

/**
 * 接口幂等性的 -- 分布式锁实现
 */
@Slf4j
@Aspect
@Component
public class ReqSubmitAspect {

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(com.laiease.common.annotation.Ide)")
    public void idePointCut() {
    }

    @Around("idePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 使用分布式锁 机制-实现
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        Ide ide = method.getAnnotation(Ide.class);
        int lockSeconds = ide.lockTime();

        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        AssertUtils.notNull(request, "request can not null");

        // 获取请求的凭证,本项目中使用的JWT,可对应修改
        String token = request.getHeader("Token");
        String requestURI = request.getRequestURI();

        String key = getIdeKey(token, requestURI);
        String clientId = CmUtil.getUUID();
        
        // 获取锁
        boolean lock = redisLock.tryLock(key, clientId, lockSeconds);
        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (lock) {
            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;
            try {
                // 执行进程
                result = joinPoint.proceed();
            } finally {
                // 解锁
                redisLock.releaseLock(key, clientId);
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }
            return result;
        } else {
            // 获取锁失败,认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            throw  new RuntimeException("重复请求,请稍后再试!");
        }
    }

    private String getIdeKey(String token, String requestURI) {
        return token + requestURI;
    }
}

 

3:redis 分布式锁工具类

@Component
public class RedisLock {

  private static final Long RELEASE_SUCCESS = 1L;
  private static final String LOCK_SUCCESS = "OK";
  private static final String SET_IF_NOT_EXIST = "NX";
  // 当前设置 过期时间单位, EX = seconds; PX = milliseconds
  private static final String SET_WITH_EXPIRE_TIME = "EX";
  //lua
  private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

  @Autowired
  private StringRedisTemplate redisTemplate;


  /**
   * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
   * 对于 Redis 集群则无法使用
   * <p>
   * 支持重复,线程安全
   *
   * @param lockKey  加锁键
   * @param clientId 加锁客户端唯一标识(采用UUID)
   * @param seconds  锁过期时间
   * @return
   */
  public boolean tryLock(String lockKey, String clientId, long seconds) {
      return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
//            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
          Object nativeConnection = redisConnection.getNativeConnection();
          RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
          byte[] keyByte = stringRedisSerializer.serialize(lockKey);
          byte[] valueByte = stringRedisSerializer.serialize(clientId);

          // lettuce连接包下 redis 单机模式
          if (nativeConnection instanceof RedisAsyncCommands) {
              RedisAsyncCommands connection = (RedisAsyncCommands) nativeConnection;
              RedisCommands commands = connection.getStatefulConnection().sync();
              String result = commands.set(keyByte, valueByte, SetArgs.Builder.nx().ex(seconds));
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }
          // lettuce连接包下 redis 集群模式
          if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
              RedisAdvancedClusterAsyncCommands connection = (RedisAdvancedClusterAsyncCommands) nativeConnection;
              RedisAdvancedClusterCommands commands = connection.getStatefulConnection().sync();
              String result = commands.set(keyByte, valueByte, SetArgs.Builder.nx().ex(seconds));
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }

          if (nativeConnection instanceof JedisCommands) {
              JedisCommands jedis = (JedisCommands) nativeConnection;
              String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }
          return false;
      });
  }

  /**
   * 与 tryLock 相对应,用作释放锁
   *
   * @param lockKey
   * @param clientId
   * @return
   */
  public boolean releaseLock(String lockKey, String clientId) {
      DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
      redisScript.setScriptText(RELEASE_LOCK_SCRIPT);
      redisScript.setResultType(Integer.class);
//        Integer execute = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), clientId);

      Object execute = redisTemplate.execute((RedisConnection connection) -> connection.eval(
              RELEASE_LOCK_SCRIPT.getBytes(),
              ReturnType.INTEGER,
              1,
              lockKey.getBytes(),
              clientId.getBytes()));
      if (RELEASE_SUCCESS.equals(execute)) {
          return true;
      }
      return false;
  }
}
posted @ 2020-01-02 10:37  不死码农  阅读(5626)  评论(0编辑  收藏  举报