Java组件-rateLimiter

限流器

google guava的限流器采用类似令牌桶的设计

SmoothBursty限流器

  • 平滑突发限流器,对于一次性需要多个资源的require,如果现存资源不够时允许此次只需等待下一个令牌生产即可获取所有资源,允许一定的突发流量,故本次计算需要的等待时间将对下一次请求生效(标注一)。(次require不等待那么长,但是下一个require则需要)

主要通过变量storedPermits(可用令牌数)、nextFreeTicketMicros(下一次生产令牌的时间戳)控制令牌的发放;

关键属性

/** 桶中当前拥有的令牌数. */
double storedPermits;

/** 桶中最多可以保存多少秒存入的令牌数 */
double maxBurstSeconds;

/** 桶中能存储的最大令牌数,等于storedPermits*maxBurstSeconds. */
double maxPermits;

/** 放入令牌的时间间隔*/
double stableIntervalMicros;

/** 下次可获取令牌的时间点,可以是过去也可以是将来的时间点*/
private long nextFreeTicketMicros = 0L;

关键方法

  void resync(long nowMicros) {
    // 当前时间已经超过下一个生产令牌的时间
    if (nowMicros > nextFreeTicketMicros) {
      // 计算到现在为止需要生产的令牌数
      double newPermits = (nowMicros - nextFreeTicketMicros) / coolDownIntervalMicros();
      // 与桶容量比较,得出此时桶中应该装的令牌数
      storedPermits = min(maxPermits, storedPermits + newPermits);
      // 下一次生产令牌的时间,这里用nowMicros,并不精确
      nextFreeTicketMicros = nowMicros;
    }
  }

对 storedPermits 与 nextFreeTicketMicros 进行了调整,如果当前时间晚于 nextFreeTicketMicros,则计算这段时间内产生的令牌数,累加到 storedPermits 上,并更新下次可获取令牌时间 nextFreeTicketMicros 为当前时间。即更新桶中的令牌数量,并计算下次生产令牌的时间戳,创建限流器、require资源时都会先调用该方法

// permits-获取资源的数量,timeout-最大等待时长
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) {
    // 确保timeout >= 0
    long timeoutMicros = max(unit.toMicros(timeout), 0);
    checkPermits(permits);
   
    long microsToWait;
    // 每次require是synchronized加锁
    synchronized (mutex()) {
      // 获取当前时间,用于计算令牌数,属于关键数据之一
      long nowMicros = stopwatch.readMicros();
      // 通过计算,预先判断最大的等待时长内是否有令牌可用,为上文标注一所述问题买单
      // 如果突发流量需要的资源数太多,导致下次生产令牌的时间太超前,将导致一段时间内无令牌可用
      if (!canAcquire(nowMicros, timeoutMicros)) {
        return false;
      } else {
        // 计算获取permits资源数需要等待的时长,
        microsToWait = reserveAndGetWaitLength(permits, nowMicros);
      }
    }
    stopwatch.sleepMicrosUninterruptibly(microsToWait);
    return true;
  }

获取资源方法,