マイクロサービスにおけるサーキットブレーカー

エンジニアリング

前回の「リトライと冪等性」に引き続き、マイクロサービスを運用するにあたって有用なテクニックであるサーキットブレーカーについてです。

カスケード障害

マイクロサービスにおいて、downstreamのサービスが障害を起こしている時、upstreamのサービスがリクエストを送ってもエラーが返ってくるか、そもそも場合によってはコネクションを確立できません。

そうした場合、ユーザーにもエラーが返ることになりますが、そうするとユーザーは手動でリトライしてくる可能性があります。また、前回説明したようにサービスにリトライが実装されていると、自動でリクエストが再送信される場合もあります。

そうなると、障害を起こしているサービスのupstreamであったサービスも通常時よりも多くのリクエストを受け取る可能性があります。

また、障害を起こしているサービスとのコネクションを確立しようとする際にも、設定されたタイムアウト値までリクエスト毎にプロセスを動かす必要が出てきます。

結果として、障害を起こしているサービスのクライアントとなるサービスにおいても、CPUやメモリの使用率が上昇し、最悪の場合ダウンしてしまうことがあります。

これが再帰的にシステム全体に波及することをカスケード障害と呼びます。

マイクロサービスにおいて、サービス数が多くなるとどれか一つのサービスが障害を起こす確率は高くなってしまうので、カスケード障害を防ぐ対策を取ることが重要になってきます。

サーキットブレーカー

サーキットブレーカーと聞くと電気回路を思い出しますが、ソフトウェアエンジニアリングにおけるサーキットブレーカー(circuit breaker)とはカスケード障害を防ぐためのデザインパターンの一種です。

サーキットブレーカーはdownstreamのサービスから特定のエラーが特定の条件で発生した場合にOpenになり、トリガーされるとリクエストを受け取ってもdownstreamにさらにリクエストをすることなくエラーや代替値を返却します。

例えば、注文を受け取るサービスがあり、そのサービスは外部の発注サービスにリクエストを送るというアーキテクチャを考えます。

外部の発注サービスがダウンし500エラーを一定期間連続で返却してきた時、注文を受け取るサービスはサーキットブレーカーをOpenにし、それ以上外部の発注サービスにリクエストを送ることなく、エラーを返すようにします。

サーキットブレーカーの状態

基本的にサーキットブレーカーには以下の状態があります。

  • Closed:通常の状態。リクエストはdownstreamサービスに送られる。
  • Open:障害が検知された状態。リクエストはdownstreamサービスに送られることなく、エラーを返す。
  • Half-open:Open状態から一定時間経過後、一部のリクエストを試行的に送信し、成功すればClosedに戻る。

サーキットブレーカーの実装によっては他の状態も存在することがありますが、基本的にはこの3つの状態間を遷移します。

サーキットブレーカーのメリット

サーキットブレーカーのメリットとしては、先に挙げたようなカスケード障害の予防だけではなく、ユーザーの待ち時間を減らされるということも考えられます。

通常、サービスがdownstreamのサービスにリクエストを送る際、まずネットワークのコネクションを確立する必要がありますが、それぞれがお互いの状態をリアルタイムで把握しているわけではないため、障害時でもコネクションを繋ごうとします。

その際にタイムアウト値が設定されており、その時間までサービスはレスポンスを返せないため、ユーザーにとっても何かしらのレスポンスを得るまでに時間がかかってしまうことになります。

サーキットブレーカーを利用すると、そもそもコネクションを繋ぎにいこうとする前にエラーを返すため、ユーザーに即時にエラーを返すことができます。

実装例

JavaやKotlinなどのJVM言語においては、Resilience4というOSSにサーキットブレーカーが含まれています。

CircuitBreaker
Getting started with resilience4j-circuitbreaker

@CircuitBreakerというアノテーションを関数に付与することでサーキットブレーカーを簡単に利用することができます。

@CircuitBreaker(name="foo", fallbackMethod="orderFallback")
fun order(items: List[Item]) {
  // ... do something
}

fun orderFallback(items: List[Item], t: Throwable) {
  // ... do something
}

ソフトウェアアーキテクチャにもよりますが、RepositoryクラスをCircuitBreakerクラスでラップしてServiceクラスから使用するようにしたり、実際の使用方法は色々あると思います。

まとめ

サーキットブレーカーはマイクロサービスにおいて、障害時にリクエストをdownstreamサービスに送る前にエラーを返すことによりカスケード障害を防ぐためのデザインパターンでした。

コメント

タイトルとURLをコピーしました