1. 概要

このチュートリアルでは、Resilience4jライブラリについて説明します。

ライブラリは、リモート通信のフォールトトレランスを管理することにより、復元力のあるシステムの実装を支援します。

ライブラリはHystrixに触発されていますが、はるかに便利なAPIと、Rate Limiter(頻繁なリクエストをブロックする)、Bulkhead(同時リクエストが多すぎるのを避ける)などの他の多くの機能を提供します。

2. Mavenのセットアップ

まず、ターゲットモジュールを pom.xml (eg ここにサーキットブレーカーを追加します)

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>0.12.1</version>
</dependency>

ここでは、サーキットブレーカーモジュールを使用しています。 すべてのモジュールとその最新バージョンは、 MavenCentralにあります。

次のセクションでは、ライブラリで最も一般的に使用されるモジュールについて説明します。

3. サーキットブレーカ

このモジュールでは、上記のresilience4j-circuitbreaker依存関係が必要であることに注意してください。

サーキットブレーカーパターンは、リモートサービスがダウンしたときの障害のカスケードを防ぐのに役立ちます。

何度も試行に失敗した後、サービスが利用できない/オーバーロードされていると見なし、それ以降のすべてのリクエストを熱心に拒否することができます。 このようにして、失敗する可能性のある呼び出しのシステムリソースを節約できます。

Resilience4jでそれを実現する方法を見てみましょう。

まず、使用する設定を定義する必要があります。 最も簡単な方法は、デフォルト設定を使用することです。

CircuitBreakerRegistry circuitBreakerRegistry
  = CircuitBreakerRegistry.ofDefaults();

カスタムパラメータを使用することもできます。

CircuitBreakerConfig config = CircuitBreakerConfig.custom()
  .failureRateThreshold(20)
  .ringBufferSizeInClosedState(5)
  .build();

ここでは、レートのしきい値を20%に設定し、5回の呼び出し試行の最小数を設定しました。

次に、 CircuitBreaker オブジェクトを作成し、それを介してリモートサービスを呼び出します。

interface RemoteService {
    int process(int i);
}

CircuitBreakerRegistry registry = CircuitBreakerRegistry.of(config);
CircuitBreaker circuitBreaker = registry.circuitBreaker("my");
Function<Integer, Integer> decorated = CircuitBreaker
  .decorateFunction(circuitBreaker, service::process);

最後に、これがJUnitテストでどのように機能するかを見てみましょう。

サービスの呼び出しを10回試みます。 呼び出しが最低5回試行され、20% ofの呼び出しが失敗するとすぐに停止したことを確認できるはずです。

when(service.process(any(Integer.class))).thenThrow(new RuntimeException());

for (int i = 0; i < 10; i++) {
    try {
        decorated.apply(i);
    } catch (Exception ignore) {}
}

verify(service, times(5)).process(any(Integer.class));

3.1。 サーキットブレーカ状態と設定

CircuitBreaker は、次の3つの状態のいずれかになります。

  • CLOSED –すべてが正常で、短絡は発生しません
  • OPEN –リモートサーバーがダウンしており、リモートサーバーへのすべての要求が短絡しています
  • HALF_OPEN – OPEN状態に入ってから設定された時間が経過し、 CircuitBreaker により、リモートサービスがオンラインに戻ったかどうかを確認する要求が可能になります

次の設定を構成できます。

  • CircuitBreaker が開き、短絡呼び出しを開始する故障率のしきい値
  • CircuitBreakerが半分開いた状態に切り替わる前に開いたままにする時間を定義する待機時間
  • CircuitBreakerが半分開いているか閉じているときのリングバッファのサイズ
  • CircuitBreakerイベントを処理するカスタムCircuitBreakerEventListener
  • カスタム述語は、例外が失敗としてカウントされ、失敗率が上がるかどうかを評価します。

4. レートリミッター

前のセクションと同様に、この機能にはresilience4j-ratelimiterの依存関係が必要です。

名前が示すように、この機能により、一部のサービスへのアクセスを制限できます。 そのAPIはCircuitBreakerのと非常によく似ています。Registry Config 、およびLimiterクラスがあります。

外観の例を次に示します。

RateLimiterConfig config = RateLimiterConfig.custom().limitForPeriod(2).build();
RateLimiterRegistry registry = RateLimiterRegistry.of(config);
RateLimiter rateLimiter = registry.rateLimiter("my");
Function<Integer, Integer> decorated
  = RateLimiter.decorateFunction(rateLimiter, service::process);

これで、レートリミッターの構成に準拠するために、必要に応じて装飾されたサービスブロックですべての呼び出しが行われます。

次のようなパラメータを設定できます。

  • 制限更新の期間
  • 更新期間の権限制限
  • 許可期間のデフォルトの待機

5. バルクヘッド

ここでは、最初にresilience4j-bulkhead依存関係が必要になります。

特定のサービスへの同時呼び出しの数を制限することが可能です。

Bulkhead APIを使用して、最大数の1つの同時呼び出しを構成する例を見てみましょう。

BulkheadConfig config = BulkheadConfig.custom().maxConcurrentCalls(1).build();
BulkheadRegistry registry = BulkheadRegistry.of(config);
Bulkhead bulkhead = registry.bulkhead("my");
Function<Integer, Integer> decorated
  = Bulkhead.decorateFunction(bulkhead, service::process);

この構成をテストするために、モックサービスのメソッドを呼び出します。

次に、Bulkheadが他の呼び出しを許可しないことを確認します。

CountDownLatch latch = new CountDownLatch(1);
when(service.process(anyInt())).thenAnswer(invocation -> {
    latch.countDown();
    Thread.currentThread().join();
    return null;
});

ForkJoinTask<?> task = ForkJoinPool.commonPool().submit(() -> {
    try {
        decorated.apply(1);
    } finally {
        bulkhead.onComplete();
    }
});
latch.await();
assertThat(bulkhead.isCallPermitted()).isFalse();

次の設定を構成できます。

  • バルクヘッドで許可される並列実行の最大量
  • 飽和バルクヘッドに入ろうとしたときにスレッドが待機する最大時間

6. リトライ

この機能を使用するには、resilience4j-retryライブラリをプロジェクトに追加する必要があります。

Retry APIを使用して、失敗した呼び出し自動的に再試行できます。

RetryConfig config = RetryConfig.custom().maxAttempts(2).build();
RetryRegistry registry = RetryRegistry.of(config);
Retry retry = registry.retry("my");
Function<Integer, Void> decorated
  = Retry.decorateFunction(retry, (Integer s) -> {
        service.process(s);
        return null;
    });

次に、リモートサービス呼び出し中に例外がスローされる状況をエミュレートし、ライブラリが失敗した呼び出しを自動的に再試行するようにします。

when(service.process(anyInt())).thenThrow(new RuntimeException());
try {
    decorated.apply(1);
    fail("Expected an exception to be thrown if all retries failed");
} catch (Exception e) {
    verify(service, times(2)).process(any(Integer.class));
}

次のように構成することもできます。

  • 最大試行回数
  • 再試行前の待機時間
  • 障害後の待機間隔を変更するカスタム関数
  • 例外によって呼び出しが再試行されるかどうかを評価するカスタム述語

7. キャッシュ

キャッシュモジュールには、resilience4j-cacheの依存関係が必要です。

初期化は、他のモジュールとは少し異なります。

javax.cache.Cache cache = ...; // Use appropriate cache here
Cache<Integer, Integer> cacheContext = Cache.of(cache);
Function<Integer, Integer> decorated
  = Cache.decorateSupplier(cacheContext, () -> service.process(1));

ここで、キャッシングは使用される JSR-107 Cache 実装によって行われ、Resilience4jはそれを適用する方法を提供します。

関数を装飾するためのAPI( Cache.decorateFunction(Function)など)はなく、APIはSupplierおよびCallableタイプのみをサポートすることに注意してください。

8. TimeLimiter

このモジュールでは、resilience4j-timelimiter依存関係を追加する必要があります。

TimeLimiterを使用して、リモートサービスの呼び出しにかかる時間を制限することができます。

実例を示すために、1ミリ秒のタイムアウトを構成してTimeLimiterを設定しましょう。

long ttl = 1;
TimeLimiterConfig config
  = TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(ttl)).build();
TimeLimiter timeLimiter = TimeLimiter.of(config);

次に、Resilience4jが予想されるタイムアウトで Future.get()を呼び出すことを確認しましょう。

Future futureMock = mock(Future.class);
Callable restrictedCall
  = TimeLimiter.decorateFutureSupplier(timeLimiter, () -> futureMock);
restrictedCall.call();

verify(futureMock).get(ttl, TimeUnit.MILLISECONDS);

CircuitBreakerと組み合わせることもできます。

Callable chainedCallable
  = CircuitBreaker.decorateCallable(circuitBreaker, restrictedCall);

9. アドオンモジュール

Resilience4jは、一般的なフレームワークやライブラリとの統合を容易にする多数のアドオンモジュールも提供します。

よく知られている統合のいくつかは次のとおりです。

  • Spring Boot – resilience4j-spring-bootモジュール
  • Ratpack – resilience4j-ratpackモジュール
  • レトロフィット– resilience4j-retrofitモジュール
  • Vertx – resilience4j-vertxモジュール
  • Dropwizard – resilience4j-metricsモジュール
  • Prometheus – resilience4j-prometheusモジュール

10. 結論

この記事では、Resilience4jライブラリのさまざまな側面を調べ、サーバー間通信におけるさまざまなフォールトトレランスの懸念に対処するためにそれを使用する方法を学びました。

いつものように、上記のサンプルのソースコードは、GitHubにあります。