Guava 是Google 开源的 Java 类库,提供了一个工具类 RateLimiter。我们先来看看 RateLimiter的使用,让你对限流有个感官的印象。假设我们有一个线程池,它每秒只能处理两个任务,如果提交的任务过快,可能导致系统不稳定,这个时候就需要用到限流。
在下面的示例代码中,我们创建了一个流速为 2 个请求 / 秒的限流器,这里的流速该怎么理解呢?直观地看,2 个请求 / 秒指的是每秒最多允许 2 个请求通过限流器,其实在Guava中,流速还有更深一层的意思:是一种匀速的概念,2 个请求 / 秒等价于 1 个请求/500 毫秒。
在向线程池提交任务之前,调用 acquire() 方法就能起到限流的作用。通过示例代码的执行结果,任务提交到线程池的时间间隔基本上稳定在 500 毫秒。
/* 限流器流速:2 个请求 / 秒 2 RateLimiter limiter = */
RateLimiter.create( 2.0 );
/* 执行任务的线程池 */
ExecutorService es = Executors
.newFixedThreadPool( 1 );
/* 记录上一次执行时间 */
prev = System.nanoTime();
/* 测试执行 20 次 */
for ( int i = 0; i < 20; i++ )
{
/* 限流器限流 */
limiter.acquire();
/* 提交任务异步执行 */
es.execute( () - > {
long cur = System.nanoTime();
/* 打印时间间隔:毫秒 */
System.out.println(
(cur - prev) / 1000_000 );
prev = cur;
} );
}
输出结果 :
...
500
499
499
500
499
经典限流算法:令牌桶算法
Guava 的限流器使用上还是很简单的,那它是如何实现的呢?Guava 采用的是令牌桶算法,其核心是要想通过限流器,必须拿到令牌。也就是说,只要我们能够限制发放令牌的速率,那么就能控制流速了。令牌桶算法的详细描述如下:
- 令牌以固定的速率添加到令牌桶中,假设限流的速率是 r/ 秒,则令牌每 1/r 秒会添加一个;
- 假设令牌桶的容量是 b ,如果令牌桶已满,则新的令牌会被丢弃;
- 请求能够通过限流器的前提是令牌桶中有令牌。
这个算法中,限流的速率 r 还是比较容易理解的,但令牌桶的容量 b 该怎么理解呢?b 其实是 burst 的简写,意义是限流器允许的最大突发流量