ソフトウェアシステムは、おそらくネットワーク上の異なるマシン上で、異なるプロセスで実行されているソフトウェアにリモートコールを行うのが一般 メモリ内呼び出しとリモート呼び出しの大きな違いの1つは、リモート呼び出しが失敗したり、タイムアウト制限に達するまで応答なしでハングしたりする可能性があることです。 さらに悪いことに、応答しないサプライヤーに多くの発信者がいる場合、重要なリソースが不足し、複数のシステム間でカスケード障害が発生する可能性が 彼の優れた本のリリースItでは、Michael Nygardはこの種の壊滅的なカスケードを防ぐために遮断器パターンを普及させました。
回路ブレーカーの背後にある基本的な考え方は非常に簡単です。 保護された関数呼び出しをcircuit breakerオブジェクトにラップし、障害を監視します。 障害が一定のしきい値に達すると、遮断器はトリップし、遮断器へのそれ以上の呼び出しはすべてエラーで戻り、保護された呼び出しはまったく行われ 通常、回路ブレーカーがトリップした場合、何らかの種類のモニターアラートが必要になります。
タイムアウトから保護するRubyでのこの動作の簡単な例を次に示します。
私は保護された呼び出しであるブロック(ラムダ)でブレーカを設定しました。
cb = CircuitBreaker.new {|arg| @supplier.func arg}
ブレーカはブロックを格納し、さまざまなパラメータ(しきい値、タイムアウト、および監視用)を初期化し、ブレーカを閉じた状態にリセットします。
クラスサーキットブレーカー。..
attr_accessor :invocation_timeout, :failure_threshold, :monitor def initialize &block @circuit = block @invocation_timeout = 0.01 @failure_threshold = 5 @monitor = acquire_monitor reset end
回路遮断器を呼び出すと、回路が閉じている場合は基礎となるブロックが呼び出されますが、開いている場合はエラーが返されます
# client code aCircuitBreaker.call(5)
class CircuitBreaker。..
def call args case state when :closed begin do_call args rescue Timeout::Error record_failure raise $! end when :open then raise CircuitBreaker::Open else raise "Unreachable Code" end end def do_call args result = Timeout::timeout(@invocation_timeout) do @circuit.call args end reset return result end
タイムアウトが発生した場合は、失敗カウンタをインクリメントし、成功した呼び出しはゼロにリセットします。
クラスサーキットブレーカー。..
def record_failure @failure_count += 1 @monitor.alert(:open_circuit) if :open == state end def reset @failure_count = 0 @monitor.alert :reset_circuit end
ブレーカの状態を、故障回数としきい値
class CircuitBreakerと比較して決定します。..
def state (@failure_count >= @failure_threshold) ? :open : :closed end
この単純な回路ブレーカーは、回路が開いているときに保護された呼び出しを回避しますが、物事が再び順調になったときにリセットするために外部の介入が必要になります。 これは、建物内の電気回路遮断器を使用した合理的なアプローチですが、ソフトウェア回路遮断器の場合、基礎となる呼び出しが再び機能しているかどうかを遮断器自体に検出させることができます。 適切な間隔の後に保護された呼び出しを再度試行し、成功した場合はブレーカをリセットすることで、この自己リセット動作を実装できます。
この種のブレーカを作成することは、リセットを試みるためのしきい値を追加し、最後のエラーの時間を保持する変数を設定することを意味します。
クラスリセットサーキットブレーカー。..
def initialize &block @circuit = block @invocation_timeout = 0.01 @failure_threshold = 5 @monitor = BreakerMonitor.new @reset_timeout = 0.1 reset end def reset @failure_count = 0 @last_failure_time = nil @monitor.alert :reset_circuit end
現在、3番目の状態があります-ハーフオープン-つまり、回路は問題が修正されているかどうかを確認するために試行として実際の呼び出しを行う準備がで
クラスリセットサーキットブレーカー。..
def state case when (@failure_count >= @failure_threshold) && (Time.now - @last_failure_time) > @reset_timeout :half_open when (@failure_count >= @failure_threshold) :open else :closed end end
ハーフオープン状態でコールを要求すると、試行コールが発生し、成功した場合はブレーカーをリセットするか、失敗した場合はタイムアウトを再起動します。
クラスリセットサーキットブレーカー。..
def call args case state when :closed, :half_open begin do_call args rescue Timeout::Error record_failure raise $! end when :open raise CircuitBreaker::Open else raise "Unreachable" end end def record_failure @failure_count += 1 @last_failure_time = Time.now @monitor.alert(:open_circuit) if :open == state end
この例は簡単な説明ですが、実際にはサーキットブレーカはもう少し機能とパラメータ化を提供します。 多くの場合、ネットワーク接続の失敗など、保護された呼び出しが発生する可能性のあるエラーの範囲から保護されます。 すべてのエラーが回路をトリップするわけではなく、いくつかは通常の障害を反映し、通常のロジックの一部として対処する必要があります。
トラフィックが多い場合、最初のタイムアウトを待っているだけで多くの呼び出しに問題が発生する可能性があります。 リモート呼び出しはしばしば遅いので、futureまたはpromiseを使用して各呼び出しを別のスレッドに配置して、結果が戻ったときに結果を処理することをお勧 これらのスレッドをスレッドプールから描画することで、スレッドプールが使い果たされたときに回路が切断されるように手配できます。
この例では、ブレーカーをトリップする簡単な方法を示しています。 より洗練されたアプローチでは、エラーの頻度を見て、たとえば50%の故障率を取得するとトリップすることがあります。 タイムアウトのしきい値は10、接続障害のしきい値は3など、さまざまなエラーのしきい値が異なる場合もあります。
私が示した例は、同期呼び出しのための回路ブレーカですが、回路ブレーカは非同期通信にも役立ちます。 ここでの一般的な手法は、すべての要求をキューに配置することです。 この場合、キューがいっぱいになると回線が切断されます。
それ自体では、サーキットブレーカは、失敗する可能性のある操作に縛られたリソースを削減するのに役立ちます。 クライアントのタイムアウトを待つことを避け、回路が壊れていると、苦労しているサーバーに負荷がかかることを避けます。 ここでは、サーキットブレーカの一般的なケースであるリモートコールについて説明しますが、システムの一部を他の部分の障害から保護したい場合に使用で
サーキットブレーカは、監視のための貴重な場所です。 ブレーカの状態の変化は記録され、ブレーカはより深い監視のために状態の詳細を明らかにする必要があります。 ブレーカーの動作は、多くの場合、環境内のより深いトラブルについての警告の良い源です。 操作のスタッフはブレーカを旅行するか、または再調節できるべきである。
ブレーカは単独では価値がありますが、それらを使用するクライアントはブレーカの障害に反応する必要があります。 リモート呼び出しと同様に、障害が発生した場合に何をすべきかを検討する必要があります。 それはあなたが実行している操作に失敗しますか、またはあなたができる回避策はありますか? クレジットカードの承認は、後で対処するためにキューに置くことができ、いくつかのデータを取得する失敗は、表示するのに十分な古いデータを示すことに
さらに読む
netflixの技術ブログには、多くのサービスを持つシステムの信頼性を向上させるための有用な情報がたくさん含まれています。 彼らのDependencyコマンドは、サーキットブレーカとスレッドプールの制限の使用について話しています。
Netflixには、分散システムのレイテンシとフォールトトレランスに対処するための洗練されたツールであるオープンソースのHystrixがあります。 Ruby、Java、Grails Plugin、C#、AspectJ、およびScalaには、他にもサーキットブレーカパターンのオープンソースの実装があります
謝辞
Pavel Shpakはサンプルコード
のバグを発見し、報告しました