Contents

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()方法,它**接受TimeOutTimeUnit作为参数。**

当没有可用许可时调用此方法将导致它等待指定的时间然后超时——如果在超时内没有足够的可用许可。

当在给定的超时时间内没有可用的许可时,它返回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