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

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

1. обзор

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

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

2. Maven Dependency

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


    com.google.guava
    guava
    22.0

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

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

Допустим, мы хотимlimit the rate of execution of the doSomeLimitedOperation() to 2 times per second.

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

RateLimiter rateLimiter = RateLimiter.create(2);

Затем, чтобы получить разрешение на выполнение отRateLimiter,, нам нужно вызвать методacquire():

rateLimiter.acquire(1);

Чтобы проверить, что это работает, мы сделаем два последующих вызова регулируемого метода:

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 разрешений за одну операцию.

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

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

Calling the acquire() without an argument is the same as calling it with a one as an argument - будет пытаться получить одно разрешение.

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

APIRateLimiter также имеет очень полезный методacquire(), которыйaccepts a timeout and TimeUnit as arguments.

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

Если в течение заданного времени ожидания нет доступных разрешений, он возвращаетfalse.. Еслиacquire() завершается успешно, этоreturns истинно:

@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 project - это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.