レジリエンス機能

7.0 の時点で、コア Spring Framework には共通の回復力機能、特にメソッド呼び出し用の @Retryable および @ConcurrencyLimit アノテーションとプログラムによる再試行のサポートが含まれています。

@Retryable

@Retryable (Javadoc) は、個々のメソッド (メソッドレベルで宣言されたアノテーションを使用)、または特定のクラス階層内のすべてのプロキシ呼び出しメソッド (型レベルで宣言されたアノテーションを使用) の再試行特性を指定するアノテーションです。

@Retryable
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

デフォルトでは、例外がスローされると、メソッド呼び出しが再試行されます。最初の失敗後、最大 3 回の再試行 (maxRetries = 3) が行われ、試行間の遅延は 1 秒になります。

@Retryable メソッドは少なくとも 1 回呼び出され、最大 maxRetries 回再試行されます。ここで、maxRetries は再試行の最大回数です。具体的には、total attempts = 1 initial attempt + maxRetries attempts です。

例: maxRetries が 4 に設定されている場合、@Retryable メソッドは少なくとも 1 回、最大 5 回呼び出されます。

必要に応じて、各メソッドごとにカスタマイズできます。たとえば、includes 属性と excludes 属性を使用して、再試行する例外を絞り込むことができます。指定された例外型は、失敗した呼び出しによってスローされた例外だけでなく、ネストされた原因とも照合されます。

@Retryable(MessageDeliveryException.class)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}
@Retryable(MessageDeliveryException.class) は @Retryable(includes = MessageDeliveryException.class) のショートカットです。

高度な使用例では、@Retryable の predicate 属性を介してカスタム MethodRetryPredicate を指定できます。述語は、Method と指定された Throwable に基づいて、たとえば Throwable のメッセージをチェックすることによって、失敗したメソッド呼び出しを再試行するかどうかを決定するために使用されます。

カスタム述語は includes および excludes と組み合わせることができますが、カスタム述語は常に includes および excludes が適用された後に適用されます。

または、4 回の再試行と、若干のジッターを伴う指数バックオフ戦略の場合:

@Retryable(
  includes = MessageDeliveryException.class,
  maxRetries = 4,
  delay = 100,
  jitter = 10,
  multiplier = 2,
  maxDelay = 1000)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

最後になりましたが、@Retryable はリアクティブ戻り型を持つリアクティブメソッドでも機能し、パイプラインを Reactor の再試行機能で装飾します。

@Retryable(maxRetries = 4, delay = 100)
public Mono<Void> sendNotification() {
    return Mono.from(...); (1)
}
1 この生の Mono は再試行仕様で装飾されます。

さまざまな特性の詳細については、@Retryable (Javadoc) で使用可能なアノテーション属性を参照してください。

@Retryable のいくつかの属性には、上記の例で使用されている特別に型指定されたアノテーション属性の代替として、プロパティプレースホルダーと SpEL サポートを提供する String バリアントがあります。

@ConcurrencyLimit

@ConcurrencyLimit (Javadoc) は、個々のメソッド (メソッドレベルで宣言されたアノテーションを使用)、または特定のクラス階層内のすべてのプロキシ呼び出しメソッド (型レベルで宣言されたアノテーションを使用) の同時実行制限を指定するアノテーションです。

@ConcurrencyLimit(10)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

これは、制限に達した場合にアクセスをブロックするスレッドプールまたは接続プールのプールサイズ制限の効果と同様に、同時に多数のスレッドからターゲットリソースへのアクセスが防止されることを目的としています。

オプションで制限を 1 に設定して、ターゲットの Bean インスタンスへのアクセスを効果的にロックすることもできます。

@ConcurrencyLimit(1)
public void sendNotification() {
    this.jmsClient.destination("notifications").send(...);
}

このような制限は、一般的にスレッドプールの制限がない仮想スレッドで特に有用です。非同期タスクの場合、これは SimpleAsyncTaskExecutor (Javadoc) で制約できます。同期呼び出しの場合、このアノテーションは ConcurrencyThrottleInterceptor (Javadoc) を通じて同等の動作を提供します。ConcurrencyThrottleInterceptor (Javadoc) は、Spring Framework 1.0 以降、AOP フレームワークを用いたプログラム的な使用のために提供されています。

@ConcurrencyLimit には、上記の int ベースの例の代替として、プロパティプレースホルダーと SpEL サポートを提供する limitString 属性もあります。

回復力のある方法の実現

Spring のコアとなるアノテーションベースの機能の多くと同様に、@Retryable と @ConcurrencyLimit はメタデータとして設計されており、適用するか無視するかを選択できます。レジリエンスアノテーションの処理を有効にする最も便利な方法は、対応する @Configuration クラスで @EnableResilientMethods (Javadoc) を宣言することです。

あるいは、コンテキストで RetryAnnotationBeanPostProcessor または ConcurrencyLimitBeanPostProcessor Bean を定義することで、これらのアノテーションを個別に有効にすることもできます。

プログラムによる再試行のサポート

ApplicationContext に登録された Bean 内のメソッドの再試行セマンティクスを指定するための宣言的なアプローチを提供する @Retryable とは対照的に、RetryTemplate (Javadoc) は任意のコードブロックを再試行するためのプログラム API を提供します。

具体的には、RetryTemplate は構成された RetryPolicy (Javadoc) に基づいて Retryable (Javadoc) 操作を実行し、場合によっては再試行します。

var retryTemplate = new RetryTemplate(); (1)

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1 暗黙的に RetryPolicy.withDefaults() を使用します。

デフォルトでは、再試行可能な操作は、スローされたすべての例外に対して再試行されます。最初の失敗後、最大 3 回の再試行 (maxRetries = 3) が行われ、試行間の遅延は 1 秒になります。

再試行回数のみをカスタマイズする必要がある場合は、以下に示すように RetryPolicy.withMaxRetries() ファクトリメソッドを使用できます。

再試行可能な操作は、少なくとも 1 回実行され、最大 maxRetries 回再試行されます。ここで、maxRetries は再試行の最大回数です。具体的には、total attempts = 1 initial attempt + maxRetries attempts です。

例: maxRetries が 4 に設定されている場合、再試行可能な操作は少なくとも 1 回、最大 5 回呼び出されます。

var retryTemplate = new RetryTemplate(RetryPolicy.withMaxRetries(4)); (1)

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1RetryPolicy.withMaxRetries(4) を明示的に使用します。

再試行する例外の種類を絞り込む必要がある場合は、includes() および excludes() ビルダーメソッドを使用できます。指定された例外の種類は、失敗した操作によってスローされた例外や、ネストされた原因と照合されます。

var retryPolicy = RetryPolicy.builder()
        .includes(MessageDeliveryException.class) (1)
        .excludes(...) (2)
        .build();

var retryTemplate = new RetryTemplate(retryPolicy);

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));
1 含める例外の種類を 1 つ以上指定します。
2 除外する例外型を 1 つ以上指定します。

高度な使用例では、RetryPolicy.Builder の predicate() メソッドを介してカスタム Predicate<Throwable> を指定できます。また、述語は、たとえば Throwable のメッセージをチェックすることによって、特定の Throwable に基づいて失敗した操作を再試行するかどうかを決定するために使用されます。

カスタム述語は includes および excludes と組み合わせることができますが、カスタム述語は常に includes および excludes が適用された後に適用されます。

次の例は、4 回の再試行と、少しのジッターを伴う指数バックオフ戦略を使用して RetryPolicy を構成する方法を示しています。

var retryPolicy = RetryPolicy.builder()
        .includes(MessageDeliveryException.class)
        .maxRetries(4)
        .delay(Duration.ofMillis(100))
        .jitter(Duration.ofMillis(10))
        .multiplier(2)
        .maxDelay(Duration.ofSeconds(1))
        .build();

var retryTemplate = new RetryTemplate(retryPolicy);

retryTemplate.execute(
        () -> jmsClient.destination("notifications").send(...));

RetryListener (Javadoc) を RetryTemplate に登録して、主要な再試行フェーズ (再試行前、再試行後など) 中に公開されたイベントに反応することができます。また、CompositeRetryListener (Javadoc) を介して複数のリスナーを構成することもできます。

RetryPolicy のファクトリメソッドとビルダー API は、一般的な構成シナリオのほとんどをカバーしていますが、カスタム RetryPolicy を実装することで、再試行をトリガーする例外の種類と使用する BackOff (Javadoc) 戦略を完全に制御できます。また、RetryPolicy.Builder の backOff() メソッドを介して、カスタマイズされた BackOff 戦略を構成することもできます。