Hystrixの紹介

1概要

典型的な分散システムは、互いに協力し合う多くのサービスから成ります。

これらのサービスは失敗したり応答が遅れたりする傾向があります。サービスが失敗すると、他のサービスがパフォーマンスに影響を与えたり、アプリケーションの他の部分にアクセスできなくなったり、最悪の場合はアプリケーション全体が停止する可能性があります。

もちろん、アプリケーションの回復力とフォールトトレランスを向上させるのに役立つ解決策があります。そのようなフレームワークの1つがHystrixです。

Hystrixフレームワークライブラリは、フォールトトレランスとレイテンシトレランスを提供することによってサービス間の相互作用を制御するのを助けます。障害のあるサービスを分離し、障害の連鎖的な影響を停止することによって、システム全体の回復力を向上させます。

この一連の記事では、サービスまたはシステムに障害が発生したときにHystrixがどのようにして問題を解決するのか、そしてこれらの状況でHystrixが何を達成できるのかを調べることから始めます。

2簡単な例

Hystrixがフォールトおよびレイテンシトレランスを提供する方法は、リモートサービスへの呼び出しを分離してラップすることです。

この簡単な例では、呼び出しを HystrixCommandの run()__メソッドにラップします。

class CommandHelloWorld extends HystrixCommand<String> {

    private String name;

    CommandHelloWorld(String name) {
        super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
        this.name = name;
    }

    @Override
    protected String run() {
        return "Hello " + name + "!";
    }
}

そして以下のように呼び出しを実行します。

@Test
public void givenInputBobAndDefaultSettings__whenCommandExecuted__thenReturnHelloBob(){
    assertThat(new CommandHelloWorld("Bob").execute(), equalTo("Hello Bob!"));
}

3 Mavenのセットアップ

MavenプロジェクトでHystrixを使用するには、プロジェクト pom.xml にNetflixの hystrix-core rxjava-core の依存関係が必要です。

<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
    <version>1.5.4</version>
</dependency>

最新版は常にhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22hystrix-core%22[here]にあります。

<dependency>
    <groupId>com.netflix.rxjava</groupId>
    <artifactId>rxjava-core</artifactId>
    <version>0.20.7</version>
</dependency>

このライブラリの最新版は、常にhttps://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22rxjava-core%22[here]にあります。

4リモートサービスの設定

実世界の例をシミュレートすることから始めましょう。

  • 以下の例では、 RemoteServiceTestSimulator クラスはリモートサーバー上のサービスを表します。一定時間後にメッセージで応答する方法があります。この待機は、リモートシステムでの時間のかかるプロセスのシミュレーションであり、結果として呼び出し側サービスへの応答が遅れることを想像できます。

class RemoteServiceTestSimulator {

    private long wait;

    RemoteServiceTestSimulator(long wait) throws InterruptedException {
        this.wait = wait;
    }

    String execute() throws InterruptedException {
        Thread.sleep(wait);
        return "Success";
    }
}
  • そして、これが RemoteServiceTestSimulator を呼び出すサンプルクライアントです。

サービスへの呼び出しは分離され、 HystrixCommandの run()__メソッドにラップされています。

class RemoteServiceTestCommand extends HystrixCommand<String> {

    private RemoteServiceTestSimulator remoteService;

    RemoteServiceTestCommand(Setter config, RemoteServiceTestSimulator remoteService) {
        super(config);
        this.remoteService = remoteService;
    }

    @Override
    protected String run() throws Exception {
        return remoteService.execute();
    }
}

呼び出しは、 RemoteServiceTestCommand オブジェクトのインスタンスに対して execute() メソッドを呼び出すことによって実行されます。

次のテストは、これがどのように行われるかを示しています。

@Test
public void givenSvcTimeoutOf100AndDefaultSettings__whenRemoteSvcExecuted__thenReturnSuccess()
  throws InterruptedException {

    HystrixCommand.Setter config = HystrixCommand
      .Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroup2"));

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(100)).execute(),
      equalTo("Success"));
}

これまで、リモートサービス呼び出しを HystrixCommand オブジェクトにラップする方法を見てきました。以下のセクションでは、リモートサービスが悪化し始めたときの状況に対処する方法を見てみましょう。

5リモートサービスと防御的プログラミングを使用した作業

5.1. タイムアウト付きの防御的プログラミング

リモートサービスへの呼び出しのタイムアウトを設定することは一般的なプログラミング方法です。

HystrixCommand にタイムアウトを設定する方法と、それが短絡によってどのように役立つかを調べることから始めましょう:

@Test
public void givenSvcTimeoutOf5000AndExecTimeoutOf10000__whenRemoteSvcExecuted__thenReturnSuccess()
  throws InterruptedException {

    HystrixCommand.Setter config = HystrixCommand
      .Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest4"));

    HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
    commandProperties.withExecutionTimeoutInMilliseconds(10__000);
    config.andCommandPropertiesDefaults(commandProperties);

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
      equalTo("Success"));
}

上記のテストでは、タイムアウトを500ミリ秒に設定して、サービスの応答を遅らせています。また、 HystrixCommand の実行タイムアウトを10,000ミリ秒に設定しているため、リモートサービスが応答するのに十分な時間を確保できます。

実行タイムアウトがサービスタイムアウト呼び出しより短い場合に何が起こるかを見てみましょう:

@Test(expected = HystrixRuntimeException.class)
public void givenSvcTimeoutOf15000AndExecTimeoutOf5000__whenRemoteSvcExecuted__thenExpectHre()
  throws InterruptedException {

    HystrixCommand.Setter config = HystrixCommand
      .Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest5"));

    HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
    commandProperties.withExecutionTimeoutInMilliseconds(5__000);
    config.andCommandPropertiesDefaults(commandProperties);

    new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(15__000)).execute();
}

バーを下げ、実行タイムアウトを5,000ミリ秒に設定したことに注目してください。

このサービスは5,000ミリ秒以内に応答すると予想していますが、15,000ミリ秒後に応答するように設定しています。テストを実行したときに気付いた場合、テストは15,000ミリ秒待つのではなく、5,000ミリ秒後に終了し、__HystrixRuntimeExceptionがスローされます。

  • これは、Hystrixがレスポンスに対して設定されたタイムアウトより長く待たないことを示しています。これにより、Hystrixによって保護されているシステムの応答性が向上します。

以下のセクションでは、スレッドが使い果たされないようにスレッドプールサイズを設定する方法を検討し、その利点について説明します。

5.2. 制限付きスレッドプールを使用した防御的プログラミング

サービスコールのタイムアウトを設定しても、リモートサービスに関連するすべての問題が解決されるわけではありません。

リモートサービスの応答が遅くなっても、通常のアプリケーションはそのリモートサービスを呼び出し続けます。

アプリケーションは、リモートサービスが正常であるかどうかを認識せず、リクエストが来るたびに新しいスレッドが生成されます。これにより、すでに苦労しているサーバー上のスレッドが使用されることになります。

サーバー上で実行されている他のリモート呼び出しやプロセスにこれらのスレッドが必要であり、CPU使用率が急上昇しないようにするために、これが発生することは望ましくありません。

HystrixCommand でスレッドプールサイズを設定する方法を見てみましょう:

@Test
public void givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool__whenRemoteSvcExecuted
  __thenReturnSuccess() throws InterruptedException {

    HystrixCommand.Setter config = HystrixCommand
      .Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupThreadPool"));

    HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter();
    commandProperties.withExecutionTimeoutInMilliseconds(10__000);
    config.andCommandPropertiesDefaults(commandProperties);
    config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
      .withMaxQueueSize(10)
      .withCoreSize(3)
      .withQueueSizeRejectionThreshold(10));

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
      equalTo("Success"));
}

上記のテストでは、最大キューサイズ、コアキューサイズ、およびキュー拒否サイズを設定しています。スレッドの最大数が10に達し、タスクキューが10のサイズに達すると、 Hystrix は要求の拒否を開始します。

コアサイズは、スレッドプール内で常に生存しているスレッドの数です。

5.3. 短絡ブレーカパターンを使用した防御的プログラミング

ただし、リモートサービス呼び出しに対しては、まだ改善があります。

リモートサービスが失敗し始めた場合を考えてみましょう。

我々はそれで要求を発射し続けて、そして資源を無駄にしたくない。サービスを回復する時間を与えてから要求を再開するために、一定期間要求を停止することをお勧めします。これがショートサーキットブレーカパターンと呼ばれるものです。

Hystrixがこのパターンをどのように実装しているかを見てみましょう。

@Test
public void givenCircuitBreakerSetup__whenRemoteSvcCmdExecuted__thenReturnSuccess()
  throws InterruptedException {

    HystrixCommand.Setter config = HystrixCommand
      .Setter
      .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupCircuitBreaker"));

    HystrixCommandProperties.Setter properties = HystrixCommandProperties.Setter();
    properties.withExecutionTimeoutInMilliseconds(1000);
    properties.withCircuitBreakerSleepWindowInMilliseconds(4000);
    properties.withExecutionIsolationStrategy
     (HystrixCommandProperties.ExecutionIsolationStrategy.THREAD);
    properties.withCircuitBreakerEnabled(true);
    properties.withCircuitBreakerRequestVolumeThreshold(1);

    config.andCommandPropertiesDefaults(properties);
    config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
      .withMaxQueueSize(1)
      .withCoreSize(1)
      .withQueueSizeRejectionThreshold(1));

    assertThat(this.invokeRemoteService(config, 10__000), equalTo(null));
    assertThat(this.invokeRemoteService(config, 10__000), equalTo(null));
    assertThat(this.invokeRemoteService(config, 10__000), equalTo(null));

    Thread.sleep(5000);

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
      equalTo("Success"));

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
      equalTo("Success"));

    assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(),
      equalTo("Success"));
}
public String invokeRemoteService(HystrixCommand.Setter config, int timeout)
  throws InterruptedException {

    String response = null;

    try {
        response = new RemoteServiceTestCommand(config,
          new RemoteServiceTestSimulator(timeout)).execute();
    } catch (HystrixRuntimeException ex) {
        System.out.println("ex = " + ex);
    }

    return response;
}

上記のテストでは、さまざまなサーキットブレーカの特性を設定しました。最も重要なものは以下のとおりです。

  • CircuitBreakerSleepWindow は4,000ミリ秒に設定されています。この

サーキットブレーカウィンドウを設定し、時間間隔を定義します。 その後、リモートサービスへの要求が再開されます ** 1に設定されている CircuitBreakerRequestVolumeThreshold

失敗率が考慮される前に必要とされる要求の最小数を定義します。

上記の設定が整っていれば、 HystrixCommand は2つの要求が失敗した後に開いた状態になります。 3番目のリクエストは、たとえサービス遅延を500ミリ秒に設定したとしても、リモートサービスにはヒットしません。Hystrixはショートし、私たちのメソッドはレスポンスとしてnullを返します。

次に、設定したスリープウィンドウの制限を超えるために Thread.sleep(5000) を追加します。これにより、 Hystrix が回路を閉じ、その後の要求が正常に行われます。

6. 結論

要約すると、Hystrixは次のように設計されています。

  1. サービスからの障害と遅延に対する保護と制御を提供します.

通常ネットワーク経由でアクセス 。一部のサービスが原因で発生した障害の連鎖を停止する

ダウン 。迅速に失敗し、迅速に回復する

  1. 可能な限り優雅に劣化させる

  2. コマンドセンターの障害をリアルタイムで監視および警告

次の記事では、Hystrixの利点とSpringフレームワークを組み合わせる方法を説明します。

完全なプロジェクトコードとすべての例はhttps://github.com/eugenp/tutorials/tree/master/hystrix[githubプロジェクト]にあります。