限流-令牌桶Guava-RateLimiter

756 阅读2分钟

如果你还不了解RateLimiter的用法,可以参考 Guava官方文档-RateLimiter类

常用的限流算法

  • 漏桶算法 请求先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
  • 令牌桶算法 系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。

RateLimiter 基本用法

@Test
public void rateLimiterTest() {
    RateLimiter rateLimiter = RateLimiter.create(0.5);
    int[] acquire = {1, 6, 2};
    for (int i = 0; i < acquire.length; i++) {
        System.out.println(System.currentTimeMillis() + "acq " + acquire[i] + ": wait " + rateLimiter.acquire(acquire[i]) + "s");
    }
}

结果如下

1561465268189acq 1: wait 0.0s 
1561465268190acq 6: wait 1.998421s
1561465270191acq 2: wait 11.996964s

观察发现,RateLimiter 有预消费的能力

Guava有两种限流模式

  • 稳定模式(SmoothBursty:令牌生成速度恒定)
  • 渐进模式(SmoothWarmingUp:令牌生成速度缓慢提升直到维持在一个稳定值)
SmoothBursty.java 几个关键参数
/**
   * The currently stored permits.
   * 当前存储的令牌数
   */
  double storedPermits;

  /**
   * The maximum number of stored permits.
   * 最大能存储的令牌数
   */
  double maxPermits;

  /**
   * The interval between two unit requests, at our stable rate. E.g., a stable rate of 5 permits
   * per second has a stable interval of 200ms.
   * 添加令牌的时间间隔
   */
  double stableIntervalMicros;

  /**
   * The time when the next request (no matter its size) will be granted. After granting a
   * request, this is pushed further in the future. Large requests push this further than small
   * requests.
   * 下一次请求可以获取令牌的起始时间
   * 由于RateLimiter允许预消费,上次请求预消费令牌后
   * 下次请求需要等待相应的时间到nextFreeTicketMicros时刻才可以获取令牌
   */
  private long nextFreeTicketMicros = 0L; // could be either in the past or future

几个关键函数

private void resync(long nowMicros) {
  // if nextFreeTicket is in the past, resync to now
  if (nowMicros > nextFreeTicketMicros) {
    storedPermits = min(maxPermits,storedPermits + (nowMicros - nextFreeTicketMicros) / stableIntervalMicros);
    nextFreeTicketMicros = nowMicros;
  }
}

根据令牌桶算法,桶中的令牌是持续生成存放的,有请求时需要先从桶中拿到令牌才能开始执行,谁来持续生成令牌存放呢?

开启一个定时任务,由定时任务持续生成令牌。这样的问题在于会极大的消耗系统资源,如,某接口需要分别对每个用户做访问频率限制,假设系统中存在6W用户,则至多需要开启6W个定时任务来维持每个桶中的令牌数

如上resync函数。该函数会在每次获取令牌之前调用,其实现思路为,若当前时间晚于nextFreeTicketMicros,则计算该段时间内可以生成多少令牌,将生成的令牌加入令牌桶中并更新数据。这样一来,只需要在获取令牌时计算一次即可