限流
限流对资源进行速率限制,即在一个时间窗口内对请求加限制。
阈值:单位时间内允许的请求量。例如 QPS 是10,就是1秒最多10个请求
拒绝策略:超过阈值的处理出策略,例如直接抛弃,排队等待。
固定窗口算法

- 将时间划分为多个窗口 
- 每个窗口内有一次请求就将计数器+1 
- 如果计数器超过了阈值,后续请求使用拒绝策略 
- 下一个时间窗口,计数器重置 
滑动窗口算法

- 将时间划分为多个区间 
- 每个区间内有一次请求就将计数器+1,一个时间窗口占据多个区间 
- 每经过一个区间的时间,抛弃最老的区间,纳入新区间 
- 如果当前窗口计数器超过阈值,后续请求使用拒绝策略 
漏桶算法

- 恒定速率通过请求 
- 当短时间内大量突发请求时,每个请求都要在队列中等待。 
令牌桶算法

- 令牌以固定速率生成 
- 生成的令牌放入桶中存放,请求到达时,尝试从桶中获取令牌,取到了令牌才可执行 
- 令牌桶空了,后续请求使用决绝策略 
是目前广泛使用的限流算法。
实际应用
Google的开源项目 guava 提供了 RateLimiter ,实现了单点的 令牌桶 限流。
// qps 2
RateLimiter rateLimiter = RateLimiter.create(2);
for (int i = 0; i < 10; i++) {
    String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
    System.out.println(time + ":" + rateLimiter.tryAcquire());
    Thread.sleep(250);
}
如果按照时间间隔区添加令牌,那么需要一个单独的线程去定时添加,如果有很多个 RateLimiter 实例,线程数也会随着增加,显然这不是一个好方法。Google 也考虑到了这个问题,在 RateLimiter 中,是在每次令牌获取时才进行计算令牌是否足够的。它通过存储的下一个令牌生成的时间,和当前获取令牌的时间差,再结合阈值,去计算令牌是否足够,同时再记录下一个令牌的生成时间以便下一次调用。
  // 申请令牌
  public double acquire(int permits) {
      //下一个令牌需要的等待时间
      long microsToWait = reserve(permits);
      //Thread.sleep
      stopwatch.sleepMicrosUninterruptibly(microsToWait);
      return 1.0 * microsToWait / SECONDS.toMicros(1 L);
  }
  //最终调用
  final long reserveEarliestAvailable(int requiredPermits, long nowMicros) {
      resync(nowMicros);
      long returnValue = nextFreeTicketMicros;
      double storedPermitsToSpend = min(requiredPermits, this.storedPermits);
      double freshPermits = requiredPermits - storedPermitsToSpend;
      long waitMicros =
          storedPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + (long)(freshPermits * stableIntervalMicros);
      this.nextFreeTicketMicros = LongMath.saturatedAdd(nextFreeTicketMicros, waitMicros);
      this.storedPermits -= storedPermitsToSpend;
      return returnValue;
  }
  //超时 重置令牌数
  void resync(long nowMicros) { // 当前微秒时间
      // 如果超时 更新存储的令牌数量
      if (nowMicros > this.nextFreeTicketMicros) {
          double newPermits = (double)(nowMicros - this.nextFreeTicketMicros) / this.coolDownIntervalMicros();
          // 更新令牌库存 storedPermits。
          this.storedPermits = Math.min(this.maxPermits, this.storedPermits + newPermits);
          // 更新下一个令牌生成时间 nextFreeTicketMicros
          this.nextFreeTicketMicros = nowMicros;
      }
  }
为了避免启动时就有阈值数量的请求过来,可以使用有预热功能的 RateLimiter。如果在预热器内未使用,它会逐渐回到冷状态。
RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit) 
