這個簡單的斷路器避免了在電路打開時進行調用,但是當一切恢復正常時需要外部干預來重置它。對于建筑物中的斷路器,這是一種合理的方法,但是對于軟件中斷路器,我們可以讓斷路器本身檢測底層調用是否可以繼續。我們可以通過在適當的間隔之后再次嘗試被保護調用來實現這種自重置行為,成功時則重置斷路器。

創建這種斷路器意味著需要為重置嘗試添加一個閾值,并設置一個變量來保存上次錯誤時間。
class ResetCircuitBreaker...
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
現在出現了第三種狀態—半開放狀態—這意味著線路已經準備好進行試驗性的真實調用,看看問題是否已經修復。
class ResetCircuitBreaker...
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
在半打開狀態下的試驗性調用,如果成功,將重置斷路器;如果失敗,將重啟超時設置。
class ResetCircuitBreaker...
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。
我所展示的示例是用于同步調用的斷路器,但是斷路器對于異步通信也很有用。這里的一種常見技術是將所有請求放在一個隊列中,服務提供者以一定速度消費該隊列—這是一種避免服務器過載的有用技術。在這種情況下,當隊列被填滿時,線路就會斷開。
就其本身而言,斷路器有助于減少在可能失敗的操作中占用資源。您可以避免客戶端的超時等待,而斷開的線路也可以避免給處于困境的服務器增加負載。我在這里討論的是遠程調用,這是使用斷路器的常見情況,但是它們可以用于任何需要保護系統部件免受其他部件故障影響的情況。
斷路器是一個有價值的監測點。斷路器中狀態的任何更改都應該被記錄,斷路器應該顯示其狀態的詳細信息,以便進行更深入的監控。斷路器的行為通常是一個很好的來源,來警告環境中更深層次的問題。操作人員應該能夠跳閘或復位斷路器。
斷路器本身是有價值的,但使用斷路器的客戶端需要對斷路器故障做出反應。與任何遠程調用一樣,您需要考慮在發生故障時應該做什么。它是否會使你正在進行的操作失敗,或者是否還有其他的解決辦法?比如信用卡授權可以放在隊列中稍后處理;通過顯示一些可以接受的舊數據來緩解無法獲取某些數據的問題。