サーキット ブレーカーは障害を検出し、障害が継続的に再発するのを防ぐ方法でそれらの障害を処理するロジックをカプセル化します。たとえば、外部サービス、データベース、または一時的に障害が発生する可能性のあるシステムの一部へのネットワーク呼び出しを処理する場合に便利です。サーキット ブレーカーを使用すると、連鎖的な障害を防止し、一時的なエラーを管理し、システム障害の中でも安定した応答性の高いシステムを維持できます。
連鎖障害は、システムの一部の障害が他の部分の障害を引き起こし、広範囲にわたる混乱につながる場合に発生します。たとえば、分散システム内のマイクロサービスが応答しなくなり、依存するサービスがタイムアウトになり、最終的には失敗する場合があります。アプリケーションの規模によっては、これらの障害の影響は壊滅的なものになる可能性があり、パフォーマンスが低下し、おそらくユーザー エクスペリエンスに影響を与える可能性があります。
サーキットブレーカー自体はテクニック/パターンであり、それが動作する 3 つの異なる状態があり、それについては後で説明します。
2.オープン状態 : オープン状態では、サーキット ブレーカーはターゲット サービスへの接続を試行せずに、すべての受信リクエストを即座に失敗させます。この状態になるのは、障害が発生したサービスのさらなる過負荷を防ぎ、回復する時間を与えるためです。事前定義されたタイムアウトの後、サーキット ブレーカーは半開状態に移行します。関連のある例は次のとおりです。オンライン ストアで、すべての購入試行が失敗するという突然の問題が発生したと想像してください。システムに負荷がかかるのを避けるため、ストアは新規購入リクエストの受け付けを一時的に停止します。
3.ハーフオープン状態 : ハーフオープン状態では、サーキット ブレーカーは、(構成可能な) 限られた数のテスト リクエストをターゲット サービスに渡すことを許可します。これらのリクエストが成功すると、回路は閉じた状態に戻ります。それらが失敗すると、回路はオープン状態に戻ります。上で示したオープン状態のオンライン ストアの例では、オンライン ストアは問題が修正されたかどうかを確認するために数回の購入試行を許可し始めます。これらの数回の試みが成功した場合、ストアはサービスを完全に再開し、新しい購入リクエストを受け付けます。
この図は、サーキット ブレーカーが サービス B へのリクエストが成功したかどうかを確認しようとして失敗/中断した場合を示しています。
その後のフォローアップ図は、サービス B へのテスト リクエストが成功し、回線が閉じられ、その後のすべての呼び出しが再び サービス B にルーティングされることを示しています。
注 : サーキット ブレーカーの主要な設定には、障害しきい値 (回線を開くために必要な障害の数)、オープン状態のタイムアウト、および半オープン状態でのテスト リクエストの数が含まれます。状態。
この記事を読み進めるには Go の予備知識が必要であることに言及することが重要です。
他のソフトウェア エンジニアリング パターンと同様に、サーキット ブレーカーはさまざまな言語で実装できます。ただし、この記事では Golang での実装に焦点を当てます。 goresilience、go-resiliency、gobreak など、この目的で使用できるライブラリがいくつかありますが、ここでは特に gobreak ライブラリの使用に焦点を当てます。
プロのヒント : gobreak パッケージの内部実装を確認できます。ここで確認してください。
外部 API への呼び出しを処理するためにサーキット ブレーカーが実装されている単純な Golang アプリケーションを考えてみましょう。この基本的な例は、サーキット ブレーカー手法を使用して外部 API 呼び出しをラップする方法を示しています。
いくつか重要なことに触れてみましょう:
サーキット ブレーカーの実装を検証するために、いくつかの単体テストを作成してみましょう。理解するために最も重要な単体テストのみを説明します。完全なコードについては、ここで確認できます。
t.Run("FailedRequests", func(t *testing.T) { // Override callExternalAPI to simulate failure callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
//Simulates the circuit breaker being open, //wait for the defined timeout, //then check if it closes again after a successful request. t.Run("RetryAfterTimeout", func(t *testing.T) { // Simulate circuit breaker opening callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 4; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } // Wait for timeout duration time.Sleep(settings.Timeout + 1*time.Second) //We expect that after the timeout period, //the circuit breaker should transition to the half-open state. // Restore original callExternalAPI to simulate success callExternalAPI = func() (int, error) { resp, err := http.Get(server.URL) if err != nil { return 0, err } defer resp.Body.Close() return resp.StatusCode, nil } _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } if cb.State() != gobreaker.StateHalfOpen { t.Fatalf("expected circuit breaker to be half-open, got %v", cb.State()) } //After verifying the half-open state, another successful request is simulated to ensure the circuit breaker transitions back to the closed state. for i := 0; i < int(settings.MaxRequests); i++ { _, err = cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err != nil { t.Fatalf("expected no error, got %v", err) } } if cb.State() != gobreaker.StateClosed { t.Fatalf("expected circuit breaker to be closed, got %v", cb.State()) } })
t.Run("ReadyToTrip", func(t *testing.T) { failures := 0 settings.ReadyToTrip = func(counts gobreaker.Counts) bool { failures = int(counts.ConsecutiveFailures) return counts.ConsecutiveFailures > 2 // Trip after 2 failures } cb = gobreaker.NewCircuitBreaker(settings) // Simulate failures callExternalAPI = func() (int, error) { return 0, errors.New("simulated failure") } for i := 0; i < 3; i++ { _, err := cb.Execute(func() (interface{}, error) { return callExternalAPI() }) if err == nil { t.Fatalf("expected error, got none") } } if failures != 3 { t.Fatalf("expected 3 consecutive failures, got %d", failures) } if cb.State() != gobreaker.StateOpen { t.Fatalf("expected circuit breaker to be open, got %v", cb.State()) } })
サーキット ブレーカーの実装に指数バックオフ戦略を追加することで、さらに一歩進めることができます。この記事では、指数関数的バックオフ戦略の例を示すことで、シンプルかつ簡潔に説明します。ただし、サーキット ブレーカーには、負荷制限、バルクヘッディング、フォールバック メカニズム、コンテキスト、キャンセルなど、言及する価値のある他の高度な戦略もあります。これらの戦略は基本的に回路ブレーカーの堅牢性と機能を強化します。以下は、指数関数的バックオフ戦略の使用例です:
指数関数的バックオフ
指数バックオフを備えたサーキットブレーカー
いくつかの点を明確にしましょう:
カスタム バックオフ関数:exponentialBackoff 関数は、ジッターを伴う指数バックオフ戦略を実装します。基本的には試行回数に基づいてバックオフ時間を計算し、再試行のたびに遅延が指数関数的に増加することを保証します。
再試行の処理: /api ハンドラーでわかるように、ロジックには、指定された試行回数 (試行回数 := 5) まで外部 API の呼び出しを試行するループが含まれています。試行が失敗するたびに、再試行する前に、exponentialBackoff 関数によって決定される期間待機します。
サーキット ブレーカーの実行: サーキット ブレーカーはループ内で使用されます。外部 API 呼び出しが成功した場合 (err == nil)、ループは中断され、成功した結果が返されます。すべての試行が失敗すると、HTTP 503 (サービス利用不可) エラーが返されます。
カスタム バックオフ戦略をサーキット ブレーカーの実装に統合することは、一時的なエラーをより適切に処理することを目的としています。再試行間の遅延が増加することで、障害が発生したサービスの負荷が軽減され、回復に時間がかかるようになります。上記のコードで明らかなように、exponentialBackoff 関数は、外部 API を呼び出すときに再試行間の遅延を追加するために導入されました。
さらに、リアルタイム監視とアラート用の Prometheus などのツールを使用して、メトリクスとロギングを統合し、サーキット ブレーカーの状態変化を監視できます。簡単な例を次に示します:
Go に高度な戦略を備えたサーキット ブレーカー パターンを実装する
ご覧のとおり、次のことが完了しました:
プロのヒント : Go の init 関数は、main 関数またはパッケージ内のその他のコードが実行される前にパッケージの状態を初期化するために使用されます。この場合、init 関数は requestCount メトリックを Prometheus に登録します。これにより、基本的に Prometheus がこのメトリックを認識し、アプリケーションの実行が開始されるとすぐにデータの収集を開始できるようになります。
障害カウンターを増やし、いつ回路をトリップするかを決定する ReadyToTrip 関数などのカスタム設定を使用してサーキット ブレーカーを作成します。
OnStateChange は状態の変化をログに記録し、対応するプロメテウス メトリクスを増分します
Prometheus メトリクスを /metrics エンドポイントで公開します
この記事の締めくくりとして、回路ブレーカーが回復力と信頼性の高いシステムを構築する上でどのように大きな役割を果たすかを理解していただければ幸いです。カスケード障害を予防的に防止することで、マイクロサービスと分散システムの信頼性を強化し、逆境に直面してもシームレスなユーザー エクスペリエンスを保証します。
スケーラビリティを考慮して設計されたシステムには、障害を適切に処理し、迅速に回復する戦略を組み込む必要があることに注意してください — Oluwafemi、2024
元々は、2024 年 6 月 7 日に https://oluwafemiakinde.dev で公開されました。
以上がGo のサーキット ブレーカー: カスケード障害を阻止するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。