Quick Guide to the Guava RateLimiter – Guava速率限制器的快速指南

最后修改: 2017年 7月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this article, we’ll be looking at the RateLimiter class from the Guava library.

在这篇文章中,我们将关注RateLimiter类,它来自Guava库。

The RateLimiter class is a construct that allows us to regulate the rate at which some processing happens. If we create a RateLimiter with N permits – it means that process can issue at most N permits per second.

RateLimiter类是一个结构,允许我们调节一些处理发生的速度。如果我们创建一个有N个许可的RateLimiter–这意味着该进程每秒最多可以发出N个许可。

2. Maven Dependency

2.Maven的依赖性

We’ll be using Guava’s library:

我们将使用Guava的库。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

The latest version can be found here.

最新版本可以在这里找到。

3. Creating and Using RateLimiter

3.创建和使用RateLimiter

Let’s say that we want to limit the rate of execution of the doSomeLimitedOperation() to 2 times per second.

假设我们想doSomeLimitedOperation()的执行率限制在每秒2次。

We can create a RateLimiter instance using its create() factory method:

我们可以使用其create() factory方法创建一个RateLimiterinstance。

RateLimiter rateLimiter = RateLimiter.create(2);

Next, in order to get an execution permit from the RateLimiter, we need to call the acquire() method:

接下来,为了从RateLimiter获得执行许可,我们需要调用acquire()方法。

rateLimiter.acquire(1);

In order to check that works, we’ll make 2 subsequent calls to the throttled method:

为了检查是否有效,我们将对节流方法进行两次后续调用。

long startTime = ZonedDateTime.now().getSecond();
rateLimiter.acquire(1);
doSomeLimitedOperation();
rateLimiter.acquire(1);
doSomeLimitedOperation();
long elapsedTimeSeconds = ZonedDateTime.now().getSecond() - startTime;

To simplify our testing, let’s assume that doSomeLimitedOperation() method is completing immediately.

为了简化我们的测试,我们假设doSomeLimitedOperation()方法是立即完成的。

In such case, both invocations of the acquire() method should not block and the elapsed time should be less or below one second – because both permits can be acquired immediately:

在这种情况下,acquire()方法的两次调用都不应该阻塞,而且经过的时间应该少于或低于一秒钟–因为两个许可证都可以立即获得。

assertThat(elapsedTimeSeconds <= 1);

Additionally, we can acquire all permits in one acquire() call:

此外,我们可以在一次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);
}

This can be useful if, for example, we need to send 100 bytes per second. We can send one hundred times one byte acquiring one permit at a time. On the other hand, we can send all 100 bytes at once acquiring all 100 permits in one operation.

这可能是有用的,例如,如果我们需要每秒发送100个字节。我们可以发送一百次一个字节,每次获得一个许可。另一方面,我们可以一次发送所有100个字节,在一次操作中获得所有100个许可证。

4. Acquiring Permits in a Blocking Way

4.以阻挠的方式获得许可

Now, let’s consider a slightly more complex example.

现在,让我们考虑一个稍微复杂的例子。

We’ll create a RateLimiter with 100 permits. Then we’ll execute an action that needs to acquire 1000 permits. According to the specification of the RateLimiter, such action will need at least 10 seconds to complete because we’re able to execute only 100 units of action per second:

我们将创建一个有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);
}

Note, how we’re using the acquire() method here – this is a blocking method and we should be cautious when using it. When the acquire() method gets called, it blocks the executing thread until a permit is available.

注意,我们在这里使用acquire()方法–这是一个阻塞方法,我们在使用它时应该谨慎。当acquire()方法被调用时,它会阻塞执行线程,直到有一个许可证可用。

Calling the acquire() without an argument is the same as calling it with a one as an argument – it will try to acquire one permit.

在没有参数的情况下调用acquire(),与以1作为参数的情况下调用是一样的–它将尝试获取一个许可证。

5. Acquiring Permits With a Timeout

5.获得有时间限制的许可证

The RateLimiter API has also a very useful acquire() method that accepts a timeout and TimeUnit as arguments.

RateLimiterAPI也有一个非常有用的acquire()方法,接受timeoutTimeUnit作为参数。

Calling this method when there are no available permits will cause it to wait for specified time and then time out – if there are not enough available permits within the timeout.

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

When there are no available permits within the given timeout, it returns false. If an acquire() succeeds, it returns true:

如果在给定的超时内没有可用的许可证,它返回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();
}

We created a RateLimiter with one permit so trying to acquire two permits will always cause tryAcquire() to return false.

我们创建了一个只有一个许可证的RateLimiter,所以试图获取两个许可证将总是导致tryAcquire()返回false。

6. Conclusion

6.结论

In this quick tutorial, we had a look at the RateLimiter construct from the Guava library.

在这个快速教程中,我们看了一下来自Guava库的RateLimiter结构。

We learned how to use the RateLimtiter to limit the number of permits per second. We saw how to use its blocking API and we also used an explicit timeout to acquire the permit.

我们学习了如何使用RateLimtiter来限制每秒钟的许可数量。我们看到了如何使用它的阻塞式API,我们还使用了显式超时来获取许可。

As always, the implementation of all these examples and code snippets can be found in the GitHub project – this is a Maven project, so it should be easy to import and run as it is.

一如既往,所有这些例子和代码片段的实现都可以在GitHub项目中找到–这是一个Maven项目,所以应该很容易导入并按原样运行。