1. 概要

Spring Cloudは、 Netflixリボンを使用して、クライアント側の負荷分散を提供します。 リボンの負荷分散メカニズムは、再試行で補完できます。

このチュートリアルでは、この再試行メカニズムについて説明します。

まず、この機能を念頭に置いてアプリケーションを構築する必要がある理由を説明します。 次に、Spring Cloud Netflixリボンを使用してアプリケーションを構築および構成し、メカニズムを示します。

2. 動機

クラウドベースのアプリケーションでは、サービスが他のサービスにリクエストを送信するのが一般的な方法です。 しかし、このような動的で不安定な環境では、ネットワークに障害が発生したり、サービスが一時的に利用できなくなったりする可能性があります。

障害を適切に処理し、迅速に回復したいと考えています。多くの場合、これらの問題は短命です。 失敗が発生した直後に同じ要求を繰り返した場合、おそらく成功するでしょう。

この方法は、信頼性の高いクラウドアプリケーションの重要な側面の1つであるアプリケーションの復元力を改善するのに役立ちます。

それでも、再試行は悪い状況につながる可能性があるため、再試行には注意を払う必要があります。 たとえば、レイテンシーが増加する可能性がありますが、これは望ましくない場合があります。

3. 設定

再試行メカニズムを試すには、2つのSpringBootサービスが必要です。 まず、RESTエンドポイントを介して今日の天気情報を表示する天気サービスを作成します。

次に、Weatherエンドポイントを使用するクライアントサービスを定義します。

3.1. 気象局

503 HTTPステータスコード(サービスは利用できません)を使用して、時々失敗する非常に単純な天気サービスを構築しましょう。 呼び出しの数が構成可能なsuccessful.call.divisorプロパティの倍数である場合に失敗することを選択することにより、この断続的な失敗をシミュレートします。

@Value("${successful.call.divisor}")
private int divisor;
private int nrOfCalls = 0;

@GetMapping("/weather")
public ResponseEntity<String> weather() {
    LOGGER.info("Providing today's weather information");
    if (isServiceUnavailable()) {
        return new ResponseEntity<>(HttpStatus.SERVICE_UNAVAILABLE);
    }
    LOGGER.info("Today's a sunny day");
    return new ResponseEntity<>("Today's a sunny day", HttpStatus.OK);
}

private boolean isServiceUnavailable() {
    return ++nrOfCalls % divisor != 0;
}

また、サービスに対して行われた再試行の回数を監視するために、ハンドラー内にメッセージロガーがあります。

後で、気象サービスが一時的に利用できなくなったときに再試行メカニズムをトリガーするようにクライアントサービスを構成します。

3.2. クライアントサービス

2番目のサービスはSpringCloudNetflixリボンを使用します。

まず、リボンクライアント構成を定義しましょう。

@Configuration
@RibbonClient(name = "weather-service", configuration = RibbonConfiguration.class)
public class WeatherClientRibbonConfiguration {

    @LoadBalanced
    @Bean
    RestTemplate getRestTemplate() {
        return new RestTemplate();
    }

}

HTTPクライアントには@LoadBalancedという注釈が付けられています。これは、リボンとの負荷分散が必要であることを意味します。

上記の@RibbonClientアノテーションに含まれるRibbonConfigurationクラスを定義することにより、サービスの可用性を判断するためのpingメカニズムと、ラウンドロビン負荷分散戦略を追加します。

public class RibbonConfiguration {
 
    @Bean
    public IPing ribbonPing() {
        return new PingUrl();
    }
 
    @Bean
    public IRule ribbonRule() {
        return new RoundRobinRule();
    }
}

次に、サービスディスカバリを使用していないため、リボンクライアントからEurekaをオフにする必要があります。 代わりに、負荷分散に使用できるWeather-serviceインスタンスの手動で定義されたリストを使用しています。

それでは、これをすべてapplication.ymlファイルに追加しましょう。

weather-service:
    ribbon:
        eureka:
            enabled: false
        listOfServers: http://localhost:8021, http://localhost:8022

最後に、コントローラーを作成して、バックエンドサービスを呼び出すようにします。

@RestController
public class MyRestController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping("/client/weather")
    public String weather() {
        String result = this.restTemplate.getForObject("http://weather-service/weather", String.class);
        return "Weather Service Response: " + result;
    }
}

4. 再試行メカニズムの有効化

4.1. application.ymlプロパティの構成

クライアントアプリケーションのapplication.ymlファイルに気象サービスのプロパティを配置する必要があります。

weather-service:
  ribbon:
    MaxAutoRetries: 3
    MaxAutoRetriesNextServer: 1
    retryableStatusCodes: 503, 408
    OkToRetryOnAllOperations: true

上記の構成では、再試行を有効にするために定義する必要のある標準のリボンプロパティを使用しています。

  • MaxAutoRetries 同じサーバーで失敗したリクエストが再試行される回数(デフォルトは0)
  • MaxAutoRetriesNextServer 最初のサーバーを除いて試行するサーバーの数(デフォルトは0)
  • tryableStatusCodes 再試行するHTTPステータスコードのリスト
  • OkToRetryOnAllOperations このプロパティがtrueに設定されている場合、GETリクエストだけでなく、すべてのタイプのHTTPリクエストが再試行されます(デフォルト)

クライアントサービスが503(サービスを利用できません)または408(リクエストタイムアウト)の応答コードを受け取ったときに、失敗したリクエストを再試行します。

4.2. 必要な依存関係

Spring Cloud Netflixリボンは、 Spring Retry を利用して、失敗したリクエストを再試行します。

依存関係がクラスパスにあることを確認する必要があります。 それ以外の場合、失敗したリクエストは再試行されません。 Spring Bootによって管理されているため、バージョンを省略できます。

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

4.3. ロジックを実際に再試行する

最後に、実際の再試行ロジックを見てみましょう。

このため、気象サービスの2つのインスタンスが必要であり、8021ポートと8022ポートで実行します。 もちろん、これらのインスタンスは、前のセクションで定義したlistOfServersリストと一致する必要があります。

さらに、シミュレートされたサービスが異なる時間に失敗することを確認するために、各インスタンスでsuccessful.call.divisorプロパティを構成する必要があります。

successful.call.divisor = 5 // instance 1
successful.call.divisor = 2 // instance 2

次に、ポート8080でクライアントサービスを実行して、次のように呼び出します。

http://localhost:8080/client/weather

Weather-serviceのコンソールを見てみましょう。

weather service instance 1:
    Providing today's weather information
    Providing today's weather information
    Providing today's weather information
    Providing today's weather information

weather service instance 2:
    Providing today's weather information
    Today's a sunny day

したがって、数回試行した後(インスタンス1で4回、インスタンス2で2回)、有効な応答が得られました。

5. バックオフポリシーの構成

ネットワークで処理できる量よりも多くのデータが発生すると、輻輳が発生します。 これを軽減するために、バックオフポリシーを設定できます。

デフォルトでは、再試行の間に遅延はありません。その下で、SpringCloudRibbonはSpringRetryNoBackOffPolicyオブジェクトを使用します。

ただし、 RibbonLoadBalancedRetryFactory クラスを拡張することで、デフォルトの動作をオーバーライドできます。

@Component
private class CustomRibbonLoadBalancedRetryFactory 
  extends RibbonLoadBalancedRetryFactory {

    public CustomRibbonLoadBalancedRetryFactory(
      SpringClientFactory clientFactory) {
        super(clientFactory);
    }

    @Override
    public BackOffPolicy createBackOffPolicy(String service) {
        FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        fixedBackOffPolicy.setBackOffPeriod(2000);
        return fixedBackOffPolicy;
    }
}

FixedBackOffPolicyクラスは、再試行の間に固定の遅延を提供します。バックオフ期間を設定しない場合、デフォルトは1秒です。

または、ExponentialBackOffPolicyまたはExponentialRandomBackOffPolicyを設定できます。

@Override
public BackOffPolicy createBackOffPolicy(String service) {
    ExponentialBackOffPolicy exponentialBackOffPolicy = 
      new ExponentialBackOffPolicy();
    exponentialBackOffPolicy.setInitialInterval(1000);
    exponentialBackOffPolicy.setMultiplier(2); 
    exponentialBackOffPolicy.setMaxInterval(10000);
    return exponentialBackOffPolicy;
}

ここで、試行間の初期遅延は1秒です。 次に、遅延は10秒を超えずに後続の試行ごとに2倍になります:1000ミリ秒、2000ミリ秒、4000ミリ秒、8000ミリ秒、10000ミリ秒、10000ミリ秒…

さらに、 ExponentialRandomBackOffPolicy は、次の値を超えることなく、各スリープ期間にランダムな値を追加します。 したがって、1500ミリ秒、3400ミリ秒、6200ミリ秒、9800ミリ秒、10000ミリ秒、10000ミリ秒…

どちらを選択するかは、トラフィックの量と異なるクライアントサービスの数によって異なります。 固定からランダムまで、これらの戦略は、トラフィックスパイクのより良い拡散を達成するのに役立ち、再試行も少なくなります。 たとえば、多くのクライアントでは、ランダムな要素により、再試行中に複数のクライアントが同時にサービスにアクセスするのを防ぐことができます。

6. 結論

この記事では、SpringCloudNetflixリボンを使用してSpringCloudアプリケーションで失敗したリクエストを再試行する方法を学びました。 また、このメカニズムが提供する利点についても説明しました。

次に、2つのSpringBootサービスに支えられたRESTアプリケーションを介して再試行ロジックがどのように機能するかを示しました。 Spring Cloud Netflixリボンは、SpringRetryライブラリを活用することでそれを可能にします。

最後に、再試行の間にさまざまなタイプの遅延を構成する方法を確認しました。

いつものように、このチュートリアルのソースコードは、GitHubからで入手できます。