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();
}