Javaにおけるサーキットブレーカーパターン:システム耐性を強化する
別名
- フォールトトレランススイッチ
サーキットブレーカーデザインパターンの目的
サーキットブレーカーパターンは、マイクロサービスや分散システムにおけるフォールトトレランスと耐性を確保する上で重要なJavaデザインパターンです。サーキットブレーカーを使用することで、失敗する可能性の高い操作をシステムが繰り返し実行しようとするのを防ぎ、障害からの復旧とカスケード障害の防止が可能です。
サーキットブレーカーパターンの詳細な説明と現実世界の例
現実世界の例
トランザクション処理のために複数の外部決済ゲートウェイに依存するECサイトの例を考えてみましょう。決済ゲートウェイの1つが応答しなくなったり遅くなったりした場合、サーキットブレーカーパターンを使用して障害を検出し、問題のあるゲートウェイを繰り返し使用しようとするのを防ぐことができます。代わりに、他の決済ゲートウェイに迅速に切り替えたり、ユーザーにエラーメッセージを表示したりすることで、Webサイトの残りの部分は機能的で応答性を維持できます。これにより、リソースの枯渇を防ぎ、他の利用可能なサービスを通じてトランザクションを処理できるようにすることで、より良いユーザーエクスペリエンスを提供します。このように、サーキットブレーカーパターンは外部APIの障害を処理し、システムの機能性を維持します。
簡単に言うと
サーキットブレーカーは、失敗したリモートサービスを適切に処理することを可能にします。アプリケーションのすべての部分が互いに高度にデカップリングされており、1つのコンポーネントの障害が他の部分の動作停止を意味しない場合に特に役立ちます。
Wikipediaによると
サーキットブレーカーは、現代のソフトウェア開発で使用されるデザインパターンです。これは、保守中、一時的な外部システム障害、または予期しないシステム障害時に、障害を検出し、障害が常に再発するのを防ぐロジックをカプセル化するために使用されます。
Javaにおけるサーキットブレーカーパターンのプログラミング例
このJavaの例は、サーキットブレーカーパターンがリモートサービスの障害を管理し、システムの安定性を維持する方法を示しています。
ローカルファイル/画像とリモートサービスの両方を使用してデータを取得するWebアプリケーションがあると想像してください。リモートサービスは遅くなったり応答しなくなったりすることがあり、スレッド枯渇のためにアプリケーションがハングする可能性があります。サーキットブレーカーパターンは、そのような障害を検出し、アプリケーションが適切に動作を停止させるのに役立ちます。
- 遅延したリモートサービスのシミュレーション
// The DelayedRemoteService simulates a remote service that responds after a certain delay.
var delayedService = new DelayedRemoteService(serverStartTime, 5);
- サーキットブレーカーの設定
// The DefaultCircuitBreaker wraps the remote service and monitors for failures.
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2, 2000 * 1000 * 1000);
- リクエストを処理するサービスの監視
// The MonitoringService is responsible for calling the remote services.
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker, quickServiceCircuitBreaker);
// Fetch response from local resource
LOGGER.info(monitoringService.localResourceResponse());
// Fetch response from delayed service 2 times to meet the failure threshold
LOGGER.info(monitoringService.delayedServiceResponse());
LOGGER.info(monitoringService.delayedServiceResponse());
- サーキットブレーカーの状態の処理
// Fetch current state of delayed service circuit breaker after crossing failure threshold limit
LOGGER.info(delayedServiceCircuitBreaker.getState()); // Should be OPEN
// Meanwhile, the delayed service is down, fetch response from the healthy quick service
LOGGER.info(monitoringService.quickServiceResponse());
LOGGER.info(quickServiceCircuitBreaker.getState());
- 障害からの復旧
// Wait for the delayed service to become responsive
try {
LOGGER.info("Waiting for delayed service to become responsive");
Thread.sleep(5000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
// Check the state of delayed circuit breaker, should be HALF_OPEN
LOGGER.info(delayedServiceCircuitBreaker.getState());
// Fetch response from delayed service, which should be healthy by now
LOGGER.info(monitoringService.delayedServiceResponse());
// As successful response is fetched, it should be CLOSED again.
LOGGER.info(delayedServiceCircuitBreaker.getState());
- 完全な例
public static void main(String[] args) {
var serverStartTime = System.nanoTime();
var delayedService = new DelayedRemoteService(serverStartTime, 5);
var delayedServiceCircuitBreaker = new DefaultCircuitBreaker(delayedService, 3000, 2,
2000 * 1000 * 1000);
var quickService = new QuickRemoteService();
var quickServiceCircuitBreaker = new DefaultCircuitBreaker(quickService, 3000, 2,
2000 * 1000 * 1000);
//Create an object of monitoring service which makes both local and remote calls
var monitoringService = new MonitoringService(delayedServiceCircuitBreaker,
quickServiceCircuitBreaker);
//Fetch response from local resource
LOGGER.info(monitoringService.localResourceResponse());
//Fetch response from delayed service 2 times, to meet the failure threshold
LOGGER.info(monitoringService.delayedServiceResponse());
LOGGER.info(monitoringService.delayedServiceResponse());
//Fetch current state of delayed service circuit breaker after crossing failure threshold limit
//which is OPEN now
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Meanwhile, the delayed service is down, fetch response from the healthy quick service
LOGGER.info(monitoringService.quickServiceResponse());
LOGGER.info(quickServiceCircuitBreaker.getState());
//Wait for the delayed service to become responsive
try {
LOGGER.info("Waiting for delayed service to become responsive");
Thread.sleep(5000);
} catch (InterruptedException e) {
LOGGER.error("An error occurred: ", e);
}
//Check the state of delayed circuit breaker, should be HALF_OPEN
LOGGER.info(delayedServiceCircuitBreaker.getState());
//Fetch response from delayed service, which should be healthy by now
LOGGER.info(monitoringService.delayedServiceResponse());
//As successful response is fetched, it should be CLOSED again.
LOGGER.info(delayedServiceCircuitBreaker.getState());
}
例の要約
- パラメータ(`timeout`、`failureThreshold`、`retryTimePeriod`)を使用してサーキットブレーカーを初期化します。
- `closed`状態から開始します。
- 成功した呼び出しでは、状態をリセットします。
- 閾値を超える失敗が発生すると、`open`状態に移行して以降の呼び出しを防ぎます。
- リトライタイムアウト後、`half-open`状態に移行してサービスをテストします。
- `half-open`状態での成功時には`closed`状態に戻り、失敗時には`open`状態に戻ります。
プログラム出力
16:59:19.767 [main] INFO com.iluwatar.circuitbreaker.App -- Local Service is working
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is down
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is down
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- OPEN
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Quick Service is working
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- CLOSED
16:59:19.769 [main] INFO com.iluwatar.circuitbreaker.App -- Waiting for delayed service to become responsive
16:59:24.779 [main] INFO com.iluwatar.circuitbreaker.App -- HALF_OPEN
16:59:24.780 [main] INFO com.iluwatar.circuitbreaker.App -- Delayed service is working
16:59:24.780 [main] INFO com.iluwatar.circuitbreaker.App -- CLOSED
この例は、サーキットブレーカーパターンがリモートサービスの障害を管理することにより、アプリケーションの安定性と耐性を維持するのにどのように役立つかを示しています。
Javaでサーキットブレーカーパターンを使用するケース
サーキットブレーカーパターンは、以下の場合に適用できます。
- 個々のサービスの障害がシステム全体の障害につながる可能性のある分散システム
- 応答しなくなったり遅くなったりする可能性のあるサードパーティサービスやデータベースとやり取りするアプリケーション
- 1つのサービスの障害が他のサービスの可用性に影響を与える可能性のあるマイクロサービスアーキテクチャ
Javaにおけるサーキットブレーカーパターンの現実世界の応用例
- 外部サービスの障害を適切に処理するためのクラウドベースのサービス
- 大量のトランザクションと外部APIへの依存を管理するためのECプラットフォーム
- システムの安定性と応答性を維持するためのマイクロサービスアーキテクチャ
- Springサーキットブレーカーモジュール
- Netflix Hystrix API
サーキットブレーカーパターンのメリットとトレードオフ
メリット
- 失敗する可能性が高い無益な操作を実行しないようにすることで、リソースを節約します。
- 部分的なシステム障害時にアプリケーションのシステム安定性とパフォーマンスを維持するのに役立ちます。
- 失敗したサービスに繰り返しリクエストが殺到するのを防ぐことで、システムの迅速な復旧を促進します。
トレードオフ
- このパターンは、障害を検出し、サーキットブレーカーの状態を管理するための追加のロジックが必要なため、システムの複雑さが増します。
- 正しく設定されていない場合、サーキットが開いていると正当なリクエストがブロックされる可能性があるため、システムの劣化につながる可能性があります。
- 応答性と保護のバランスをとるために、閾値とタイムアウト期間を慎重に調整する必要があります。
関連パターン
- バルクヘッド:システムの異なる部分を隔離して、障害がシステム全体に広がるのを防ぐために使用できます。
- リトライパターン:サーキットを開く前に失敗した操作をリトライするために、サーキットブレーカーパターンと組み合わせて使用できます。