Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

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

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.

2. Maven Dependency

We’ll be using Guava’s library:

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

The latest version can be found here.

3. Creating and Using RateLimiter

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

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

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(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.

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:

assertThat(elapsedTimeSeconds <= 1);

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

@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.

4. Acquiring Permits in a Blocking Way

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:

@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.

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.

5. Acquiring Permits With a Timeout

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

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:

@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.

6. Conclusion

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

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.

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.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.