RateLimiter

RateLimiter

RateLimiter 类是一个构造,它允许我们调节一些处理发生的速度。如果我们创建一个有 N 个许可证的 RateLimiter,这意味着每秒钟最多可以发出 N 个许可证。比方说,我们想把 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 个字节,在一次操作中获取所有 100 个许可证。

阻塞方式获取权限

现在,让我们考虑一个稍微复杂的例子。我们将创建一个有 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() 方法和以 1 作为参数调用该方法是一样的,它将尝试获取一个许可证。

设置超时

RateLimiter API 还有一个非常有用的 acquire()方法,它接受超时和 TimeUnit 作为参数。当没有可用的许可证时,调用这个方法会使它等待指定的时间,然后超时–如果在超时内没有足够的可用许可证。当在给定的超时时间内没有可用的许可证时,它将返回 false。如果获取()成功,则返回 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();
}