1. 序章

RESTエンドポイントを介した外部サービスの呼び出しは、Feignなどのライブラリによって非常に簡単に行われた一般的なアクティビティです。 ただし、そのような呼び出し中には多くのことがうまくいかない可能性があります。 これらの問題の多くはランダムまたは一時的なものです。

このチュートリアルでは、失敗した呼び出しを再試行し、より回復力のあるRESTクライアントを作成する方法を学習します。

2. Feignクライアントのセットアップ

まず、機能を再試行して後で拡張する単純なFeignクライアントビルダーを作成しましょう。 HTTPクライアントとしてOkHttpClientを使用します。 また、GsonEncoderGsonDecoderを使用して、要求と応答をエンコードおよびデコードします。 最後に、ターゲットのURIと応答タイプを指定する必要があります。

public class ResilientFeignClientBuilder {
    public static <T> T createClient(Class<T> type, String uri) {
        return Feign.builder()
          .client(new OkHttpClient())
          .encoder(new GsonEncoder())
          .decoder(new GsonDecoder())
          .target(type, uri);
    }
}

または、Springを使用する場合は、使用可能なBeanを使用してFeignクライアントを自動配線することができます。

3. フェイグリトライアー

幸いなことに、再試行機能はFeignで作成されており、構成する必要があります。 クライアントビルダーにRetryerインターフェイスの実装を提供することでそれを行うことができます。

その最も重要なメソッドであるcontinueOrPropagate、 RetryableException を引数として受け入れ、何も返しません。 実行時に、例外をスローするか、正常に終了します(通常はスリープ後)。 例外がスローされない場合、Feignは引き続き呼び出しを再試行します。例外がスローされると、例外が伝播され、エラーが発生して呼び出しが効果的に終了します。

3.1. ナイーブな実装

1秒待った後に常に呼び出しを再試行するRetryerの非常に単純な実装を書いてみましょう。

public class NaiveRetryer implements feign.Retryer {
    @Override
    public void continueOrPropagate(RetryableException e) {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            throw e;
        }
    }
}

RetryerCloneableインターフェースを実装しているため、cloneメソッドもオーバーライドする必要がありました。

@Override
public Retryer clone() {
    return new NaiveRetryer();
}

最後に、実装をクライアントビルダーに追加する必要があります。

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .retryer(new NaiveRetryer())    
      // ...
}

または、Springを使用している場合は、NaiveRetryer@Componentアノテーションを付けるか、構成クラスで bean を定義して、残りの部分をSpringに任せることができます。仕事:

@Bean
public Retryer retryer() {
    return new NaiveRetryer();
}

3.2. デフォルトの実装

Feignは、Retryerインターフェイスの実用的なデフォルト実装を提供します。 指定された回数だけ再試行し、一定の時間間隔で開始し、再試行ごとに指定された最大値まで増やします。開始間隔100ミリ秒、最大間隔で定義しましょう。 3秒、および最大試行回数は5回です。

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
// ...
      .retryer(new Retryer.Default(100L, TimeUnit.SECONDS.toMillis(3L), 5))    
// ...
}

3.3. 再試行なし

Feignに呼び出しを再試行させたくない場合は、Retryer.NEVER_RETRY実装をクライアントビルダーに提供できます。 毎回例外を伝播するだけです。

4. 再試行可能な例外の作成

前のセクションでは、呼び出しを再試行する頻度を制御する方法を学びました。 次に、呼び出しを再試行するタイミングと、単に例外をスローするタイミングを制御する方法を見てみましょう。

4.1. ErrorDecoderおよびRetryableException

誤った応答を受信すると、Feignはそれを ErrorDecoder インターフェースのインスタンスに渡し、それをどう処理するかを決定します。 最も重要なことは、デコーダーが例外を RetryableException、のインスタンスにマップして、Retryerが呼び出しを再試行できるようにすることです。 ErrorDecoderのデフォルトの実装では、応答に「Retry-After」ヘッダーが含まれている場合にのみRetryableExeceptionインスタンスが作成されます。最も一般的には、503ServiceUnavailable応答で見つけることができます。

これはデフォルトの動作としては適切ですが、より柔軟にする必要がある場合もあります。 たとえば、外部サービスと通信している可能性があります。外部サービスは、500内部サーバーエラーでランダムに応答することがあり、それを修正する権限がありません。 次回はおそらく機能することがわかっているので、呼び出しを再試行することができます。 これを実現するには、カスタムErrorDecoder実装を作成する必要があります。

4.2. カスタムエラーデコーダーの作成

カスタムデコーダーに実装する必要があるメソッドは、decodeの1つだけです。 StringメソッドキーとResponseオブジェクトの2つの引数を受け入れます。 RetryableException のインスタンス、または実装に依存するその他の例外の場合は、例外を返します。

decode メソッドは、応答のステータスコードが500以上かどうかを確認するだけです。 その場合、RetryableExceptionが作成されます。 そうでない場合は、FeignExceptionクラスのerrorStatusファクトリ関数で作成された基本的なFeignExceptionが返されます。

public class Custom5xxErrorDecoder implements ErrorDecoder {
    @Override
    public Exception decode(String methodKey, Response response) {
        FeignException exception = feign.FeignException.errorStatus(methodKey, response);
        int status = response.status();
        if (status >= 500) {
            return new RetryableException(
              response.status(),
              exception.getMessage(),
              response.request().httpMethod(),
              exception,
              null,
              response.request());
        }
        return exception;
    }
}

この場合、例外を作成して返すのであって、スローするのではないことに注意してください。

最後に、デコーダーをクライアントビルダーに接続する必要があります。

public static <T> T createClient(Class<T> type, String uri) {
    return Feign.builder()
      // ...
      .errorDecoder(new Custom5xxErrorDecoder())
      // ...
}

5. 概要

この記事では、Feignライブラリの再試行ロジックを制御する方法を学びました。 Retryer インターフェースと、それを使用して再試行の時間と回数を操作する方法を調べました。 次に、 ErrorDecoder を作成して、再試行が必要な応答を制御します。

いつものように、すべてのコード例はGitHubにあります。