マイクロサービスにレジリエンスをもたらすHystrixを試してみる

github.com

HystrixはNetflixが開発しているオープンソースJavaライブラリで、主として分散システムにおけるサービス間のやり取りをラップして以下のような機能を提供します。

例えば外部サービスに障害があり呼び出しがエラーになる場合や、処理に時間がかかった場合にフォールバック値を返すことで呼び出し元の処理を継続できたり、外部サービスの呼び出しでエラーが多発する場合は一時的に呼び出しをショートカットして呼び出し先の回復を待ったりといった制御を行うことができます。

マイクロサービスではあるサービスの障害や遅延が他のサービスに波及する危険性がありますが。このような仕組みをサービス間の通信に導入することでサービス全体のレジリエンスの向上が期待できます。

今回はHystrixの基本的な使い方とダッシュボードに表示するところまでをやってみたので簡単に紹介したいと思います。

基本的な使い方

pom.xmlに以下の依存関係を追加します。

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

まずはHystrixのドキュメントにある簡単な例を見てみましょう。以下のように実行したい処理をHystrixCommandのサブクラスとして実装します。

public class CommandHelloWorld extends HystrixCommand<String> {

  private final String name;

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

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

ここではサンプルということで単に文字列を返す処理を行っていますが、実際は他のサービスとの通信をこのクラスで行うことになります。

コマンドは以下のようにいくつかの呼び出し方があります。

// ブロックして結果を取得
String s = new CommandHelloWorld("Bob").execute();

// Futureを取得
Future<String> f = new CommandHelloWorld("Bob").queue();

// コールバックで結果を取得
Observable<String> o = new CommandHelloWorld("Bob").observe();
o.subscribe((s) -> {
  System.out.println("onNext: " + s);
});

エラー時にも値を返すようにする

getFallback()メソッドを実装しておくとコマンドの処理で例外が発生した場合、代わりにそのメソッドの戻り値を返すことができます。

public class CommandHelloWorld extends HystrixCommand<String> {

  private final String name;

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

  @Override
  protected String run() {
    throw new RuntimeException();
  }

  @Override
  protected String getFallback() {
    return "dummy message";
  }
}

処理に時間がかかる場合はタイムアウトさせる

以下のようにコマンド生成時にコンストラクタの引数としてタイムアウト値を指定することができます。

public CommandHelloWorld(String name) {
  super(Setter
    .withGroupKey(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"))
    .andCommandPropertiesDefaults(
      HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(1000)
    ));
  this.name = name;
}

コマンドの処理が指定時間以内に完了しない場合はgetFallback()メソッドの戻り値が返されます。getFallback()メソッドが実装されていない場合はHystrixRuntimeExceptionがスローされます。

値をキャッシュする

getCacheKey()メソッドでキャッシュキーを返すようにしておくと同じキーを返す呼び出しに対してはキャッシュした値を返すようにできます。

public class CommandHelloWorld extends HystrixCommand<String> {

  private final String name;

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

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

  @Override
  protected String getCacheKey() {
    return this.name;
  }
}

ただし、キャッシュを使用する場合はHystrixRequestContextを生成しておく必要があります。サーブレットフィルタでコンテキストの初期化とシャットダウンを行うことが想定されているようです。

HystrixRequestContext context = HystrixRequestContext.initializeContext();

ドキュメントを見てもキャッシュに関する設定項目はキャッシュを使うかどうかだけでキャッシュの有効期限やエントリ数などを細かく制御することはできないのでしょうか?

ダッシュボードでモニタリングする

アプリケーションにメトリクスを取得するためのエンドポイントを組み込むことでダッシュボードに表示することもできます。hystrix-metrics-event-streamモジュールでサーブレットコンテナ用のエンドポイント実装が提供されているのでこれを使ってみます。

まずは前述のコマンドを呼び出すWebアプリケーションを作成しておきます。直接サーブレットに実装してもよいですし、サーブレットコンテナ上で稼働するものであればフレームワークを使ったものでも構いません。そのうえでpom.xmlに以下の依存関係を追加します。

<dependency>
  <groupId>com.netflix.hystrix</groupId>
  <artifactId>hystrix-metrics-event-stream</artifactId>
  <version>1.5.3</version>
</dependency>

次に作成したアプリケーションにHytrixのダッシュボードからメトリックを取得するためのエンドポイントを追加するためにweb.xmlに以下の設定を追加します。

<servlet>
  <description></description>
  <display-name>HystrixMetricsStreamServlet</display-name>
  <servlet-name>HystrixMetricsStreamServlet</servlet-name>
  <servlet-class>com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>HystrixMetricsStreamServlet</servlet-name>
  <url-pattern>/hystrix.stream</url-pattern>
</servlet-mapping>

Hystrixのダッシュボードを起動します。

$ git clone https://github.com/Netflix/Hystrix.git
$ cd Hystrix/hystrix-dashboard
$ ../gradlew jettyRun
> Building > :hystrix-dashboard:jettyRun > Running at http://localhost:7979/hystrix-dashboard

f:id:takezoe:20160616165301p:plain

モニタリング対象のWebアプリケーションのエンドポイントを追加してモニタリングを開始すると以下のようにダッシュボードに状態が表示されます。

f:id:takezoe:20160616165312p:plain

まとめ

Hystrixはこの他にも様々な機能があります。ドキュメントがかなりしっかりしているので一通り読んでみるといいと思います。

通信以外の処理をコマンド化してもよいと思いますし、導入も比較的容易なのでマイクロサービスアーキテクチャを採用しない場合でもモニタリングしたい部分や外部サービスを呼び出している部分などにピンポイントで入れておくといった使い方でも便利そうです。

なお、HystrixはJava用のライブラリなのでScalaではそのままでは使いにくかったり、ダッシュボードに情報を提供するためのエンドポイントもサーブレットコンテナ用の実装しか用意されていなかたりします。次回はHystrixをScalaで使う方法について書いてみたいと思います。