1. 概要

この記事では、GuavaライブラリのRateLimiterクラスについて説明します。

RateLimiter クラスは、何らかの処理が発生する速度を調整できるようにする構造です。 N個の許可を持つRateLimiterを作成する場合、プロセスは1秒あたり最大N個の許可を発行できることを意味します。

2. Mavenの依存関係

Guavaのライブラリを使用します。

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

最新バージョンはここにあります。

3. RateLimiterの作成と使用

doSomeLimitedOperation()の実行速度を1秒あたり2回に制限したいとします。

create()ファクトリメソッドを使用して、RateLimiterインスタンスを作成できます。

RateLimiter rateLimiter = RateLimiter.create(2);

次に、 RateLimiter、から実行許可を取得するには、 acquire()メソッドを呼び出す必要があります。

rateLimiter.acquire(1);

それが機能することを確認するために、throttledメソッドを2回続けて呼び出します。

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

テストを簡素化するために、 doSomeLimitedOperation()メソッドがすぐに完了していると仮定します。

このような場合、 acquire()メソッドの両方の呼び出しがブロックされないようにし、経過時間は1秒未満または1秒未満にする必要があります。両方の許可をすぐに取得できるためです。

assertThat(elapsedTimeSeconds <= 1);

さらに、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);
}

これは、たとえば、1秒あたり100バイトを送信する必要がある場合に役立ちます。 一度に1つの許可を取得して1バイトを100回送信できます。 一方、100バイトすべてを一度に送信して、1回の操作で100個すべての許可を取得することができます。

4. 妨害的な方法で許可を取得する

ここで、もう少し複雑な例を考えてみましょう。

100個の許可を持つRateLimiterを作成します。 次に、1000の許可を取得する必要があるアクションを実行します。 RateLimiterの仕様によると、このようなアクションは1秒あたり100ユニットのアクションしか実行できないため、完了するまでに少なくとも10秒かかります。

@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を使用して呼び出すことと同じです –1つの許可を取得しようとします。

5. タイムアウト付きの許可の取得

The RateLimiter APIも非常に便利です取得() その方法引数としてタイムアウトと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();
}

1つの許可でRateLimiterを作成したため、2つの許可を取得しようとすると、常に tryAcquire()falseを返します。

6. 結論

このクイックチュートリアルでは、GuavaライブラリからのRateLimiterコンストラクトを確認しました。

RateLimtiter を使用して、1秒あたりの許可数を制限する方法を学びました。 ブロッキングAPIの使用方法を確認し、明示的なタイムアウトを使用して許可を取得しました。

いつものように、これらすべての例とコードスニペットの実装は、 GitHubプロジェクトにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。