Guava中Ratelimiter简介
1. 概述
在本文中,我们将研究Guava库中的*RateLimiter * 类。
RateLimiter类是一个构造,它允许我们调节某些处理发生的速率。如果我们创建一个带有 N 个许可的RateLimiter——这意味着该进程每秒最多可以发布 N 个许可。
2. Maven依赖
我们将使用 Guava 的库:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.0.1-jre</version>
</dependency>
最新版本可以在这里 找到。
3. 创建和使用RateLimiter
假设我们希望将**doSomeLimitedOperation()的执行速率限制为每秒 2 次。
我们可以使用它的create()工厂方法创建一个RateLimiter实例:
RateLimiter rateLimiter = RateLimiter.create(2);
接下来,为了从RateLimiter 获得执行许可,我们需要调用*acquire()*方法:
rateLimiter.acquire(1);
为了检查是否有效,我们将对节流方法进行 2 次后续调用:
long startTime = ZonedDateTime.now().getSecond();
rateLimiter.acquire(1);
doSomeLimitedOperation();
rateLimiter.acquire(1);
doSomeLimitedOperation();
long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;
为了简化我们的测试,我们假设*doSomeLimitedOperation()*方法立即完成。
在这种情况下,*acquire()*方法的两次调用都不应阻塞,并且经过的时间应少于或少于一秒——因为可以立即获取两个许可:
assertThat(elapsedTimeSeconds <= 1);
此外,我们可以在一次*acquire()*调用中获取所有许可:
@Test
public void givenLimitedResource_whenRequestOnce_thenShouldPermitWithoutBlocking() {
// given
RateLimiter rateLimiter = RateLimiter.create(100);
// when
long startTime = ZonedDateTime.now().getSecond();
rateLimiter.acquire(100);
doSomeLimitedOperation();
long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;
// then
assertThat(elapsedTimeSeconds <= 1);
}
例如,如果我们需要每秒发送 100 个字节,这将很有用。我们可以一次发送一百次一个字节获取一个许可证。另一方面,我们可以一次发送所有 100 个字节,在一次操作中获得所有 100 个许可。
4. 以阻塞方式获取许可
现在,让我们考虑一个稍微复杂一点的例子。
我们将创建一个具有 100 个许可的RateLimiter 。然后我们将执行一个需要获得 1000 个许可的操作。根据RateLimiter 的规范,这样的动作至少需要 10 秒才能完成,因为我们每秒只能执行 100 个动作单位:
@Test
public void givenLimitedResource_whenUseRateLimiter_thenShouldLimitPermits() {
// given
RateLimiter rateLimiter = RateLimiter.create(100);
// when
long startTime = ZonedDateTime.now().getSecond();
IntStream.range(0, 1000).forEach(i -> {
rateLimiter.acquire();
doSomeLimitedOperation();
});
long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;
// then
assertThat(elapsedTimeSeconds >= 10);
}
注意,我们在这里如何使用*acquire()方法——这是一个阻塞方法,我们在使用它时应该小心。当调用acquire()*方法时,它会阻塞正在执行的线程,直到获得许可为止。
*在没有参数的情况下调用*acquire()与使用 one 作为参数调用它是一样的——它将尝试获取一个许可。
5. 超时获取许可证
RateLimiter API 还有一个非常有用的acquire()方法,它**接受TimeOut和TimeUnit作为参数。**
当没有可用许可时调用此方法将导致它等待指定的时间然后超时——如果在超时内没有足够的可用许可。
当在给定的超时时间内没有可用的许可时,它返回false。如果acquire()成功,则返回true:
@Test
public void givenLimitedResource_whenTryAcquire_shouldNotBlockIndefinitely() {
// given
RateLimiter rateLimiter = RateLimiter.create(1);
// when
rateLimiter.acquire();
boolean result = rateLimiter.tryAcquire(2, 10, TimeUnit.MILLISECONDS);
// then
assertThat(result).isFalse();
}
我们创建了一个带有一个许可的RateLimiter,因此尝试获取两个许可总是会导致tryAcquire()返回false。