zoukankan      html  css  js  c++  java
  • 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;
      }
    }
  • 相关阅读:
    demo 集合
    iOS12、iOS11、iOS10、iOS9常见适配
    gem install cocoapods ERROR: While executing gem ... (Gem::FilePermissionError)
    ios LaunchScreen.storyboard 适配启动图
    加载资源文件读取以及转换成字符串的方法
    [UIApplication sharedApplication].keyWindow和[[UIApplication sharedApplication].delegate window]区别
    婚庆手机APP
    从一部电视剧开始
    论一次使用代理模式实现共用导出报表的功能
    MySql中使用EXPLAIN查看sql的执行计划
  • 原文地址:https://www.cnblogs.com/leeego-123/p/12131852.html
Copyright © 2011-2022 走看看