Lagomのサーキットブレーカーを試してみる

Lagomを試してみるシリーズ第8回です。今回はLagomの特徴的な機能の1つであるサーキットブレーカーを試してみました。

マイクロサービスのベストプラクティスのひとつに障害の発生したサービスをシステムから切り離すことでサービス全体を停止せずに運用するためのサーキットブレーカーの導入があります。サーキットブレーカーの機能を持つプロダクトとしてはNetflixのHystrixなどが有名ですが、Lagomはこの機能をフレームワークとして提供しており、ちょっとしたコードを記述するだけで簡単に使用できてしまいます。

まずはLagomのサーキットブレーカーの動きについて説明しておきます。サーキットブレーカーは以下の3つの状態を持ちます。

  • Closed: サーキットブレーカーが閉じている、つまりサービスが問題なく稼働しておりアクティブな状態です。一定回数失敗するとOpen状態に移行します。
  • Open: サーキットブレーカーが開放されている、つまりサービスに障害があり、切り離されている状態です。Open状態から一定時間経過するとHalf-open状態に移行します。
  • Half-open: サービスの再開にトライしている状態です。サービスが正常な状態に戻ればそのままClosed状態に、引き続き失敗する場合はOpen状態に戻ります。

なお、Lagomのサーキットブレーカーはクライアント側で実装されているため、前回紹介したサービスクライアントを使用してサービスを呼び出さないと使用することができません。

サーキットブレーカーを使用するにはサービスの定義でwithCircuitBreakerを指定します。サーキットブレーカーの設定はIDごとに行うことができますのでバックエンドを共有しているサービスなど、障害時にまとめて切り離したい単位でIDを振っておくとよいでしょう。

@Override
default Descriptor descriptor() {
  // @formatter:off
  return named("helloservice").with(
    restCall(Method.GET, "/api/hello", hello())
      .withCircuitBreaker(new Descriptor.CircuitBreakerId("hello"))
  ).withAutoAcl(true);
  // @formatter:on
}

このサービスを呼び出すサービスの実装プロジェクトのapplication.confに以下のような設定を追加します。

lagom.circuit-breaker {
  default {
    # サーキットブレーカーを有効にする場合はonにする
    enabled = on
    # 指定した回数呼び出しに失敗したらOpen状態に移行する
    max-failures = 10
    # 呼び出し失敗とみなすタイムアウト時間
    call-timeout = 10s
    # Open状態からHalf-open状態に移行するまでの時間
    reset-timeout = 15s
  }
}

以下のようにサーキットブレーカーのIDごとに設定を上書きすることができます。

lagom.circuit-breaker {
  # デフォルトの設定
  default {
    enabled = on
    max-failures = 10
    call-timeout = 10s
    reset-timeout = 15s
  }
  # helloというIDのサーキットブレーカーの設定
  hello {
    enabled = on
    max-failures = 2
    call-timeout = 1s
    reset-timeout = 10s
  }
}

実際の動きを見てみます。以下のようにサービスの呼び出しが設定した回数失敗すると「Circuit breaker [hello] open」というメッセージが表示され、サーキットブレーカーが開放されていることがわかります。

[error] myservice - Exception in RestCallId{method=GET, pathPattern='/api/hoge'}
akka.pattern.CircuitBreaker$$anon$1: Circuit Breaker Timed out.
[error] myservice - Exception in RestCallId{method=GET, pathPattern='/api/hoge'}
akka.pattern.CircuitBreaker$$anon$1: Circuit Breaker Timed out.
[warn] c.l.l.i.c.CircuitBreakerMetricsImpl - Circuit breaker [hello] open

この状態でサービスを呼び出そうとすると以下のように即座にエラーが返ってきます。

[error] myservice - Exception in RestCallId{method=GET, pathPattern='/api/hoge'}
akka.pattern.CircuitBreakerOpenException: Circuit Breaker is open; calls are failing fast

reset-timeoutで設定した時間が経過した後にサービスを呼び出すと再度サービスの呼び出しにトライし、その時点でサービスが正常にレスポンスを返すことができればサーキットブレーカーはClosed状態に戻ります。

Lagomで完結していないと機能しないのが残念なところですが、別途ミドルウェアを導入しなくても簡単にサーキットブレーカーを利用できるのは便利ですね。仕組み的にLagom以外からの呼び出しに対応できないのは仕方ないですが、Lagomから外部のサービスを呼び出すときは使えそうな気がするので内部のAPIがどんな感じになっているのか気になるところです。