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

之前博文中介绍过token 机制处理 接口幂等性问题,这种方式一个问题对代码的入侵比较多,相对书写代码来讲就比较麻烦,本文介绍使用 redis 分布式锁机制解决接口幂等性问题

  1. 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ide{

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

}
  1. 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;
    }
}
  1. 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 集群则无法使用
   * 

* 支持重复,线程安全 * * @param lockKey 加锁键 * @param clientId 加锁客户端唯一标识(采用UUID) * @param seconds 锁过期时间 * @return */ public boolean tryLock(String lockKey, String clientId, long seconds) { return redisTemplate.execute((RedisCallback) redisConnection -> { // Jedis jedis = (Jedis) redisConnection.getNativeConnection(); Object nativeConnection = redisConnection.getNativeConnection(); RedisSerializer stringRedisSerializer = (RedisSerializer) 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 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; } }

你可能感兴趣的:(redis 分布式锁处理接口幂等性)