Guava RateLimiter

Google Guava提供的RateLimiter使用的是令牌桶算法。令牌桶算法的基本思想是以固定的速率生成令牌,在执行请求之前都需要从令牌桶里获取足够的令牌。当令牌数量不足的时候,请求将被阻塞进入等待状态或者直接返回失败。RateLimiter常用于限制访问资源的速率。

RateLimiter使用示例

下面是一个RateLimiter的简单使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class RateLimiterTest {
// 1秒钟产生0.5张令牌
private final static RateLimiter limiter = RateLimiter.create(0.5);

public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(5);
IntStream.range(0, 5).forEach(i -> service.submit(RateLimiterTest::testLimiter));
service.shutdown();
}

private static void testLimiter() {
System.out.println(Thread.currentThread() + " waiting " + limiter.acquire());
}
}

我们定义了一个RateLimiter实例,每秒钟产生0.5张令牌,即每2秒钟产生1张令牌。testLimiter方法中通过limiter.acquire()方法获取令牌(不带参数时默认获取1张令牌)。Executors.newFixedThreadPool(5)生成五个线程,并发调用testLimiter方法,执行代码,控制台输出如下所示:

1
2
3
4
5
Thread[pool-1-thread-1,5,main] waiting 0.0
Thread[pool-1-thread-5,5,main] waiting 1.908947
Thread[pool-1-thread-4,5,main] waiting 3.908935
Thread[pool-1-thread-3,5,main] waiting 5.908919
Thread[pool-1-thread-2,5,main] waiting 7.908808

可以看到每个线程调用时间相隔大约为2秒钟。可能你会问,为什么第一个线程没有等待2秒,直接就获取到了令牌然后执行了呢?

Guava RateLimiter允许某次请求获取超出剩余令牌数的令牌,但是下一次请求将为此付出代价,一直等到令牌亏空补上。再来看一个RateLimiter的例子:

1
2
3
4
5
6
7
8
9
public class RateLimiterTest {
public static void main(String[] args) {
RateLimiter limiter = RateLimiter.create(1);
System.out.println(limiter.acquire(4));
System.out.println(limiter.acquire(3));
System.out.println(limiter.acquire(2));
System.out.println(limiter.acquire(1));
}
}

程序输出如下:

1
2
3
4
0.0
3.996602
2.997448
2.000229

上面例子钟,一秒钟产生一张令牌,第一次请求直接取出4张令牌,所以第二次请求需要等待4/1秒才能取到令牌。经过大约4秒后,第二次请求直接取出3张令牌,所以第三次请求需要等待3/1秒后才能取到令牌,依此类推。

设置超时时间

我们可以设置等待令牌的超时时间,如果等待令牌的时间大于超时时间,将直接返回false,不再等待:

1
2
3
4
5
6
7
public class RateLimiterTest {
public static void main(String[] args) {
RateLimiter limiter = RateLimiter.create(1);
System.out.println(limiter.acquire(3));
System.out.println(limiter.tryAcquire(1, 2, TimeUnit.SECONDS));
}
}

上面例子limiter.tryAcquire设置了超时时间为2秒,由于第一次请求一次性获取了3张令牌,所以这里需要等待大约3秒钟,超出了2秒的超时时间,所以limiter.tryAcquire不会等待3秒,而是直接返回false。

请作者喝瓶肥宅水🥤

0