Conan
Conan
发布于 2023-12-29 / 66 阅读
0
0

Redis分布式锁去获取唯一Token

使用Redis的seltIfAbsent【线程安全的原子操作,不可分割,整体执行】

获取Token时,判断需要获取新Token【此处:token为空或token的有效期小于10s】,即需要刷新Token:

  1. 先获取一个刷新Token的锁

    1. 获取成功:

      1. 先获取Token,再检查是否需要刷新Token,有可能在获取锁的时候,Token刷新已经成功,这时候就不需要再次刷新Token;

      2. 删除存储Token RedisKey;

      3. 获取Token【获取成功后,会存储到Redis】;

      4. 删除锁;

    2. 获取失败,说明有现成在执行获取Token方法;

      1. 获取Token,判断是否需要刷新,

        1. 是说明需要等待其他线程获取Token完成【每次判断完之后睡眠200ms,再重新获取token,在执行循环判断是否需要刷新,执行上述逻辑】【如果循环了10次并且还未获取到正常Token,就抛出异常】

        2. 否说明已经获取完成,直接返回即可;

获取Token以及判断是否需要刷新方法

  private String getOutlookToken() {
    ValueOperations<String, String> ops = redisTemplate.opsForValue();
    String token = ops.get(OUTLOOK_REDIS_KEY);
    if (needRefresh(token, OUTLOOK_REDIS_KEY)) {
      return lockAndRefreshOutlookToken(ops);
    }
    if (StrUtil.isBlank(token)) {
      throw new BusinessException(ErrorCodeMsgEnums.SYSTEM_ERROR, "获取token异常,请稍后再试");
    }
    return token;
  }

  private boolean needRefresh(String token, String redisKey) {
    if (StrUtil.isBlank(token)) {
      return true;
    }
    Long expire = redisTemplate.getExpire(redisKey, TimeUnit.SECONDS);
    return (expire == null || expire < 10L);
  }

刷新Token方法

 private String lockAndRefreshOutlookToken(ValueOperations<String, String> ops) {
    // 获取锁
    Boolean locked =
        ops.setIfAbsent(
            OUTLOOK_REDIS_LOCK_KEY,
            LocalDateTime.now().format(DatePattern.NORM_DATETIME_MS_FORMATTER),
            20L,
            TimeUnit.SECONDS);
    String token;
    // 获取到锁
    if (Boolean.TRUE.equals(locked)) {
      try {
        // 再次检查是否需要刷新token
        token = ops.get(OUTLOOK_REDIS_KEY);
        if (needRefresh(token, OUTLOOK_REDIS_KEY)) {
          redisTemplate.delete(OUTLOOK_REDIS_KEY);
          token = outlookLogin();
        }
      } finally {
        redisTemplate.delete(OUTLOOK_REDIS_LOCK_KEY);
      }
    } else {
      // 获取锁失败,尝试 10次 获取token
      int maxRetries = 10;
      int retryCount = 0;
      token = ops.get(OUTLOOK_REDIS_KEY);
      while (needRefresh(token, OUTLOOK_REDIS_KEY)) {
        if (retryCount > maxRetries) {
          throw new BusinessException(ErrorCodeMsgEnums.SYSTEM_ERROR, "获取token失败,请稍后再试(最大次数)");
        }
        ThreadUtil.sleep(200);
        token = ops.get(OUTLOOK_REDIS_KEY);
        retryCount++;
      }
    }
    return token;
  }


评论