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;
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM