サーキットブレーカーパターン - AWS 規範ガイダンス

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

サーキットブレーカーパターン

Intent

サーキットブレーカーパターンを使用すると、呼び出しのタイムアウトまたは失敗が繰り返し発生した場合に、呼び出し元が別のサービス (呼び出し先) への呼び出しを再試行できないようにすることができます。このパターンは、呼び出し先サービスが再び稼働状態になったことを検出するためにも使用されます。

導入する理由

複数のマイクロサービスが連携してリクエストを処理していると、1 つまたは複数のサービスが使用できなくなったり、レイテンシーが大きくなったりする可能性があります。また、複雑なアプリケーションでマイクロサービスを使用している状態で 1 つのマイクロサービスが停止すると、アプリケーションの障害が発生する場合もあるでしょう。マイクロサービスがリモートプロシージャ呼び出しを介して通信しており、ネットワーク接続で一時的なエラーが発生した場合にも、障害が発生する可能性があります。(こうした一時的なエラーは、バックオフパターンで再試行することで処理できます)。さらに、同期実行中にタイムアウトや障害がカスケードされると、ユーザーエクスペリエンスの低下を招きかねません。

しかし、一時的ではなく、解決に時間がかかる障害も発生する可能性があります。例えば、呼び出し先サービスがダウンしていたり、データベースの競合によってタイムアウトが発生したりする場合などです。そのような状況で、呼び出し元サービスが呼び出しを繰り返すと、それらの再試行によってネットワーク競合が発生し、データベーススレッドプールが消費されてしまうでしょう。さらに、複数のユーザーがこのアプリケーションの利用を再試行すると、問題が悪化し、アプリケーション全体のパフォーマンス低下を招きかねません。

サーキットブレーカーパターンは、Michael Nygard 氏の著書「Release It」(2018 年出版) によって、広く知られるようになりました。この設計パターンを使用すると、呼び出しにタイムアウトや失敗が繰り返し生じた呼び出し元による再試行を防ぐことができ、呼び出し先サービスが再び利用可能になったことを検出することも可能です。

サーキットブレーカーオブジェクトは、回路に異常が生じた際に自動的に電流を遮断する電気回路ブレーカーのように動作します。電気回路ブレーカーは、障害発生時に電流を遮断 (トリップ) するものですが、同様に、サーキットブレーカーオブジェクトを呼び出し元サービスと呼び出し先サービスの間に配置すると、呼び出し先サービスが利用できない場合にトリップを実行できます。

Sun Microsystems の Peter Deutsch 氏らは、分散コンピューティングには誤解が見られると主張しており、分散アプリケーションの経験がないプログラマーは、常に誤った前提を立てると述べています。ネットワークの信頼性、ゼロレイテンシーの期待、帯域幅の制限などをポジティブな要因と思い込み、ソフトウェアアプリケーションの開発で、最低限のネットワークエラー処理しか行わないのです。

ネットワーク障害の発生中、アプリケーションは無期限に応答を待ち、自身のリソースを消費し続ける可能性があり、ネットワークが使用可能になったタイミングで操作を再試行しないと、そのパフォーマンスが低下しかねません。また、ネットワークの問題によって、データベースや外部サービスへの API 呼び出しがタイムアウトし、サーキットブレーカーがないために呼び出しが繰り返された場合、コストとパフォーマンスに影響が出ることもあります。

適用対象

このパターンは、次の場合に使用します。

  • 呼び出し元サービス側で、失敗の可能性が最も高い呼び出しを行っている。

  • 呼び出し先サービスのレイテンシーが大きく (データベース接続が遅いなど) 、呼び出し元サービス側でタイムアウトが発生している。

  • 呼び出し元サービス側で同期呼び出しを行っているが、呼び出し先のサービスが利用できないか、レイテンシーが大きい。

問題点と考慮事項

  • サービスに依存しない実装: コードの肥大化を防ぐには、マイクロサービスに依存しない API 駆動型の方法でサーキットブレーカーオブジェクトを実装すると良いでしょう。

  • 呼び出し先によるサーキットクローズ: 呼び出し先がパフォーマンスの問題や障害から回復したタイミングで、サーキットのステータスを CLOSED に更新できます。これはサーキットブレーカーパターンを拡張したもので、そうした更新が目標復旧時間 (RTO) の要件である場合は実装すると良いでしょう。

  • マルチスレッド呼び出し: 有効期限タイムアウト値には、サービスの可用性確認のために呼び出しを再度ルーティングするまでサーキットをトリップ状態にしておく期間を定義しますが、複数のスレッドで呼び出し先サービスを呼び出す場合は、最初に失敗した呼び出しによって有効期限タイムアウト値が定義されます。これを実装する場合は、後続の呼び出しによって有効期限タイムアウトがその後無限に延長されないようにする必要があります。

  • サーキットの強制開閉: システム管理者がデータベーステーブルの有効期限タイムアウト値を 更新することでサーキットを開閉できるようにしておく必要があります。

  • オブザーバビリティ: アプリケーションにはログ記録を設定し、サーキットブレーカーが開いているときに失敗した呼び出しを特定できるようにする必要があります。

実装

高レベルのアーキテクチャ

次の例では、呼び出し元が注文サービスを、呼び出し先が支払いサービスを表しています。

次の図のように、失敗が発生しない場合、サーキットブレーカーは、注文サービスからのすべての呼び出しを支払いサービスにルーティングします。

失敗がない場合のサーキットブレーカーパターン。

支払いサービスがタイムアウトすると、サーキットブレーカーは、タイムアウトを検出し、失敗を追跡します。

支払いサービスの呼び出しが失敗した場合のサーキットブレーカー。

タイムアウトが指定したしきい値を超えると、サーキットが開きます。回路が開いている場合、サーキットブレーカーオブジェクトは、呼び出しを支払いサービスにルーティングしません。注文サービスが支払いサービスを呼び出すと、すぐにエラーが返ります。

サーキットブレーカーは、支払いサービスへのルーティングを停止します。

サーキットブレーカーオブジェクトは、支払いサービスへの呼び出しが成功したかどうかを定期的に確認します。

サーキットブレーカーは、定期的に支払いサービスの呼び出しを再試行します。

支払いサービスの呼び出しが成功すると、回路は閉じ、それ以降のすべての呼び出しが支払いサービスに再度ルーティングされます。

支払いサービスが動作している場合のサーキットブレーカー。

AWS のサービスを使用した実装

サンプルソリューションでは、AWS Step Functions ワークフローを使用してサーキットブレーカーパターンを実装しています。Step Functions のステートマシンを使用すると、このパターン実装に必要な再試行機能と意思決定ベースの制御フローを設定できます。

また、このソリューションでは、Amazon DynamoDB テーブルをデータストアに使用してサーキットステータスを追跡しています。パフォーマンスを向上させるには、これを、Amazon ElastiCache (Redis OSS) などのインメモリデータストアに置き換えると良いでしょう。

サービスが別のサービスを呼び出すと、呼び出し先サービスの名前でワークフローが開始されます。ワークフローでは、DynamoDB CircuitStatus テーブル (その時点でパフォーマンスが低下しているサービスの格納先) からサーキットブレーカーのステータスを取得します。CircuitStatus に、有効期限が切れていない呼び出し先レコードが含まれている場合、サーキットは開いています。Step Functions ワークフローからは、即座に失敗のステータスが返り、ワークフローは FAIL ステートで終了します。

CircuitStatus テーブルに呼び出し先レコードが含まれていない、または期限切れレコードが含まれている場合は、サービスは稼働状態にあります。ステートマシン定義内の ExecuteLambda ステップが、パラメータ値を介して送信される Lambda 関数を呼び出します。呼び出しが成功した場合、Step Functions ワークフローは SUCCESS ステートで終了します。

AWS Step Functions および DynamoDB を使用した、サーキットブレーカーの実装。

サービスの呼び出しが失敗するか、タイムアウトが発生すると、定義した回数だけエクスポネンシャルバックオフによる再試行が発生します。再試行後もサービスの呼び出しに失敗する場合、対象サービスの CircuitStatus テーブルに ExpiryTimeStamp を持つレコードが挿入され、ワークフローは FAIL ステートで終了します。同じサービスがその後も呼び出されると、サーキットブレーカーが開いている限り、即座に失敗が返ります。また、ステートマシン定義内の Get Circuit Status ステップによって、ExpiryTimeStamp 値を基にサービスの可用性が確認され、DynamoDB の有効期限 (TTL) 機能により、期限切れ項目が CircuitStatus テーブルから削除されます。

サンプルコード

次のコードでは、GetCircuitStatus Lambda 関数を使用してサーキットブレーカーのステータスを確認しています。

var serviceDetails = _dbContext.QueryAsync<CircuitBreaker>(serviceName, QueryOperator.GreaterThan, new List<object> {currentTimeStamp}).GetRemainingAsync(); if (serviceDetails.Result.Count > 0) { functionData.CircuitStatus = serviceDetails.Result[0].CircuitStatus; } else { functionData.CircuitStatus = ""; }

次のコードは、Step Functions ワークフローの Amazon States Language ステートメントを示しています。

"Is Circuit Closed": { "Type": "Choice", "Choices": [ { "Variable": "$.CircuitStatus", "StringEquals": "OPEN", "Next": "Circuit Open" }, { "Variable": "$.CircuitStatus", "StringEquals": "", "Next": "Execute Lambda" } ] }, "Circuit Open": { "Type": "Fail" }

GitHub リポジトリ

このパターンのサンプルアーキテクチャの完全な実装については、https://github.com/aws-samples/circuit-breaker-netcore-blog にある GitHub リポジトリを参照してください。

ブログの参考情報

関連情報