Перейти к основному содержимому

Краткое руководство по Guava RateLimiter

· 4 мин. чтения

1. Обзор

В этой статье мы рассмотрим класс RateLimiter из библиотеки Guava .

Класс RateLimiter — это конструкция, которая позволяет нам регулировать скорость, с которой происходит некоторая обработка. Если мы создадим RateLimiter с N разрешениями — это означает, что процесс может выдать максимум N разрешений в секунду.

2. Зависимость от Maven

Мы будем использовать библиотеку Guava:

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

Последнюю версию можно найти здесь .

3. Создание и использование RateLimiter

Допустим, мы хотим ограничить скорость выполнения doSomeLimitedOperation() до 2 раз в секунду.

Мы можем создать экземпляр RateLimiter , используя его фабричный метод create() :

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

Кроме того, мы можем получить все разрешения в одном вызове accept() :

@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. Получение разрешений блокирующим способом

Теперь давайте рассмотрим немного более сложный пример.

Мы создадим RateLimiter со 100 разрешениями. Затем мы выполним действие, которое должно получить 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);
}

Обратите внимание, как мы используем метод Acquis() здесь — это метод блокировки, и мы должны быть осторожны при его использовании. Когда метод Acquis() вызывается, он блокирует исполняемый поток до тех пор, пока не будет доступно разрешение.

Вызов функции Acquire() без аргумента аналогичен вызову с единицей в качестве аргумента — он попытается получить одно разрешение.

5. Получение разрешений с тайм-аутом

API RateLimiter также имеет очень полезный методAcquire () , который принимает таймаут и TimeUnit в качестве аргументов.

Вызов этого метода, когда нет доступных разрешений, приведет к тому, что он будет ждать указанное время, а затем истечет время ожидания — если доступных разрешений недостаточно в течение тайм- аута.

Если в течение заданного тайм-аута нет доступных разрешений, возвращается 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.

6. Заключение

В этом кратком руководстве мы рассмотрели конструкцию RateLimiter из библиотеки Guava .

Мы научились использовать RateLimtiter для ограничения количества разрешений в секунду. Мы увидели, как использовать его блокирующий API, а также использовали явный тайм-аут для получения разрешения.

Как всегда, реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.