軟件系統對運行在不同進程或者網路中不同的機器的軟件進行遠程調用是很常見的。內存中調用和遠程調用之間的一個主要區別是,遠程調用可能會失敗,或者在達到某個超時限制之前掛起而沒有響應。更糟糕的是,如果一個響應延遲的服務提供方上有許多調用者,那么您可能會耗盡關鍵資源,導致跨多個系統的連鎖故障。michael nygard在他的優秀著作《發布》中推廣了斷路器模式,以防止這種災難性的連鎖故障。
斷路器的基本原理很簡單。您將一個受保護的函數調用封裝在一個斷路器對象中,該斷路器對象監視故障。一旦故障達到某個閾值,斷路器就會跳閘,所有對斷路器的繼續調用都會返回一個錯誤,受保護的調用也不會繼續。如果斷路器跳閘,您通常還需要通過監視器進行警報。
下面是ruby寫的一個簡單示例,用于防止超時。
我使用block (lambda)設置了斷路器,它是受保護的調用。
cb = circuitbreaker.new {|arg| @supplier.func arg}
斷路器存儲block,初始化各種參數(閾值、超時和監視功能),并將斷路器重置為關閉狀態。
class circuitbreaker...
attr_accessor :invocation_timeout, :failure_threshold, :monitor
def initialize &block
@circuit = block
@invocation_timeout = 0.01
@failure_threshold = 5
@monitor = acquire_monitor
reset
end
如果線路關閉,則調用斷路器將調用底層block,如果打開則返回錯誤
# 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
如果我們調用超時,我們故障計數器計數增加,調用成功則將其重置為零。
class circuitbreaker...
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
這個簡單的斷路器避免了在電路打開時進行調用,但是當一切恢復正常時需要外部干預來重置它。對于建筑物中的斷路器,這是一種合理的方法,但是對于軟件中斷路器,我們可以讓斷路器本身檢測底層調用是否可以繼續。我們可以通過在適當的間隔之后再次嘗試被保護調用來實現這種自重置行為,成功時則重置斷路器。
創建這種斷路器意味著需要為重置嘗試添加一個閾值,并設置一個變量來保存上次錯誤時間。
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。
我所展示的示例是用于同步調用的斷路器,但是斷路器對于異步通信也很有用。這里的一種常見技術是將所有請求放在一個隊列中,服務提供者以一定速度消費該隊列—這是一種避免服務器過載的有用技術。在這種情況下,當隊列被填滿時,線路就會斷開。
就其本身而言,斷路器有助于減少在可能失敗的操作中占用資源。您可以避免客戶端的超時等待,而斷開的線路也可以避免給處于困境的服務器增加負載。我在這里討論的是遠程調用,這是使用斷路器的常見情況,但是它們可以用于任何需要保護系統部件免受其他部件故障影響的情況。
斷路器是一個有價值的監測點。斷路器中狀態的任何更改都應該被記錄,斷路器應該顯示其狀態的詳細信息,以便進行更深入的監控。斷路器的行為通常是一個很好的來源,來警告環境中更深層次的問題。操作人員應該能夠跳閘或復位斷路器。
斷路器本身是有價值的,但使用斷路器的客戶端需要對斷路器故障做出反應。與任何遠程調用一樣,您需要考慮在發生故障時應該做什么。它是否會使你正在進行的操作失敗,或者是否還有其他的解決辦法?比如信用卡授權可以放在隊列中稍后處理;通過顯示一些可以接受的舊數據來緩解無法獲取某些數據的問題。
致謝
來源:網絡
以上是網絡信息轉載,信息真實性自行斟酌。