高可用系列特殊场景:第三方限时唯一访问令牌

一、业务特点

很多第三方服务,都使用访问令牌来做访问验证,比如某度、某信的access token,主要特征如下:

1、令牌由第三方系统发放,用于访问第三方特定资源;

2、令牌存在有效期限,过期自动失效;

3、同时只存在一个有效令牌,重新获取后之前获取到的令牌将会过期(这一点不是每个平台都严格限制,我们假设严格限制)。

二、整体设计

系统主要逻辑,主要设计目标为尽可能让用户一次以尽可能快的速度获得令牌,同时也提供前端做自动刷新策略的依据,具体设计如下:

1、分布式缓存(暂不考虑本地缓存场景,有兴趣可以讨论)最新令牌,缓存有效期低于令牌有效期;

2、先从缓存中获取,缓存中不存在、已过期或要求强制刷新,则重新获取令牌。如果需要强制,刷新前手动删除缓存;

3、获取令牌前先添加分布式锁,保障只有一个线程在获取令牌;

4、假设未抢到分布式锁,则限时等待,同时监听广播等待唤醒,超时则尝试再次从缓存中获取,如果获取不到则返回再次获取标识;

5、抢到分布式锁的线程,获取到令牌后,加入缓存,并广播令牌事件,唤醒等待中线程;

6、前端根据返回状态,若返回再次获取标识或已获得令牌但使用时返回失效时,则尝试重试再次获取;

7、当业务量大,需要本地缓存令牌时,先尝试从本地获取,刷新时需要双删本地令牌,并广播删除令牌事件,通知其他服务删除过期本地令牌。

三、代码实现(以Java为例,基于分布式缓存半伪代码)

1、主程序设计:

// 定义在最前
private static final CountDownLatch latch = new CountDownLatch(1);


// 以下真正主程序开始
// 强制刷新,删除缓存,否则尝试先从缓存获取

if (isForceRefresh) {

  delete("CacheKey");

} else {

  accToken = getFromCache();

}

// 未从缓存中获取到,或强制刷新时,尝试获取最新token

if (StringUtils.isBlank(accToken)) {

  Lock lock = getLock("LockKey");

  try {

    // 尝试获取分布式锁,未获取到锁时,快速失败,获取到锁时锁n秒(一般考虑访问到锁需要的时间)

    boolean b = lock.tryLock(-1, n, TimeUnit.SECONDS);

    if (b) {
      // 从远程获取最新访问令牌

      accToken = newToken();

    }

    else {

      // 未抢到锁,尝试等待其他线程获取到后直接获取,这里等待两次

      accToken = waitGet(1);

    }

  } catch (InterruptedException e) {

    // just ignore

  } finally {

    if (lock.isHeldByCurrentThread()) {

      if (lock.isLocked()) {

        lock.unlock();

      }

      // 如果当前线程抢到锁,则无论是否成功都广播事件唤醒其他等待中线程

      broadcastWakeupEvent("EvantKey");

    }
  } // end: finally

} // end: if (StringUtils.isBlank(accToken))



return 新令牌

2、waitGet 等待设计:

String accToken = null;

try {

  // 使用latch等待n毫秒后,重新尝试从缓存获取

  boolean isWakeup = latch.await(n, TimeUnit.MILLISECONDS);

  accToken = getFromCache();

  // 未获取到,不是被事件唤醒的,且等待次数非0,则尝试再次等待

  if (!isWakeup && StringUtils.isBlank(accToken) && count > 0) {

    accToken = waitGet(--count);

  }

  if (StringUtils.isBlank(accToken)) {

    // 未获取到,返回前端重试标识

    return retrySign();

  }

} catch (InterruptedException e) {

  // 异常打断,返回前端重试标识

  return retrySign();

}

return accToken;

3、监听获取到token事件,唤醒等待线程

latch.countDown();

本作品的版权所有权归作者所有,受法律保护。未经作者书面许可,任何个人或组织均不得以任何形式使用、复制、修改、传播、展示或在未获得授权的情况下进行商业利用。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值