摘要:斷路器模式也使系統能夠檢測出錯誤是否已被修復。斷路器模式的目的和重試模式有所不同。斷路器模式為在從錯誤中恢復的系統提供穩定性,同時降低對性能的影響。
斷路器模式
當連接到遠程服務或資源到時候,處理那些需要一段時間才能修復的系統缺陷。這能優化應用對穩定性和可靠性。
上下文和問題在分布式環境中,對遠端服務或資源的請求可能會由于諸如以下臨時性錯誤而失敗:緩慢的網絡請求,連接超時,資源被過度使用,或服務臨時不可用。通常情況下,這些錯誤能夠在短暫的中斷后自我修復。一個健壯的云端應用應該能夠通過重試模式等策略來處理這些問題。
然而,有的時候這些錯誤緣于一些未知的事件,從而需要更長的時間修復。這些錯誤可能是系統一部分無法連接,或是整個服務都響應失敗。在這些情況下,盲目的去重試之前的操作可能并沒有意義,而且也不太可能會成功,取而代之系統應該快速識別出操作失敗然后去處理這些失敗。
另外,如果一個服務非常繁忙,系統中的一部分出錯將導致級連的錯誤。例如,一個調用其他服務的操作可以設定一個超時,然后在超時后返回錯誤。然而,這個策略可能導致很多訪問這個服務的并發請求阻塞,直到超時。這些阻塞的請求可能占用了重要的系統資源,諸如內存,線程,數據庫鏈接等。因此可能導致這些資源被耗盡,進而導致其他不相干的模塊因為資源競爭而失敗。在這些情況下,直接讓這些操作失敗,然后在合適的時候再去嘗試調用這些服務,似乎是更合理的選擇。設定一個短一些的超時時長可能會有助于解決這個問題,但是又不能設定的太短而中斷那些最終可能成功的請求。
解決方案由 Michael Nygard 在其[書中](https://pragprog.com/book/mne...)普及的斷路器模式,能夠阻止應用重復的嘗試執行可能失敗的請求。這允許系統繼續運行,而不用等待那些錯誤被修復,也不用浪費 CPU 循環,因為它已經識別到該錯誤是持續性的。斷路器模式也使系統能夠檢測出錯誤是否已被修復。如果問題已經被修復,系統能夠重新調用該操作。
斷路器模式的目的和重試模式有所不同。重試模式使應用能夠重試期望成功的操作。斷路器模式阻止應用去調用很可能失敗的操作。應用可以聯合使用兩種模式。然而,重試邏輯應該能夠處理斷路器模式拋出的異常,并在斷路器指示該錯誤非短期可修復的錯誤時,停止重試。
斷路器為可能會失敗的操作充當代理的角色。這個代理監視最近發生的失敗的數量,然后用這些信息判斷是否繼續執行該操作,還是直接返回異常。
該代理可以通過一個狀態機來實現,該狀態機應模擬電子斷路器來實現以下狀態:
關閉:來自應用的請求直接路由到對應的操作。代理維護一個計數器來記錄最近失敗的次數。如果一個操作失敗,該計數器加一。如果最近失敗的次數在指定時間段內超過一個閾值,代理被設定到 開啟 狀態。同時,代理啟動一個計時器,當計時器超時后,代理被設定到 半開狀態。
設定計時器的目的是在應用重試該操作前,給系統留出時間修復導致該錯誤的問題。
開啟:從應用發送給該服務的請求直接失敗,并返回異常。
半開:允許少量的請求通過代理調用該操作。如果請求成功,系統假定之前引起操作失敗的錯誤已被修復,斷路器設定到 關閉狀態(且將失敗計數器重置)。如果任何請求失敗,斷路器便假定之前的錯誤依舊存在,然后把狀態重新置為打開,重啟超時計時器,并為系統恢復該錯誤設定更長的恢復時間。
半開 狀態有助于使恢復中的系統避免遭受突發的大量請求。在服務恢復過程中,它可能只能支撐有限數量的請求,直至恢復完全完成。在恢復過程中接收大量請求,可能會使服務超時,甚至再次失敗。
在上圖中,關閉狀態下使用的計數器是基于時間的,它會自動定期重置。這能夠使斷路器避免因偶發性失敗而切換到失敗狀態。失敗閾值設定使斷路器只有在指定的時間內失敗的次數達到了指定值后才切換到失敗狀態。半開狀態下使用的計數器用來記錄請求成功的次數。當連續成功的請求數量超過一個指定值后,斷路器將切換到 關閉狀態。如果任一調用失敗,斷路器將直接進入打開狀態,下次進入半開狀態的時候,成功計數器將被清零。
系統如何修復是屬于本模式以外的內容,可能通過重新加載數據,重啟失敗的組件,或是修復網絡問題。
斷路器模式為在從錯誤中恢復的系統提供穩定性,同時降低對性能的影響。它通過快速駁回可能失敗的請求來降低系統響應時間。如果每次斷路器切換狀態時都觸發一個時間,則可以用來監視斷路器保護部分的系統狀態,或在斷路器切換到 打開狀態時為管理員提供報警。
這個模式是可定制的,而且可適配不同類型的錯誤。例如,你可以將超時計數器的值調高,你可以將斷路器處在開狀態的初始值設為幾秒,然后如果到時后失敗未解決將超時器設為幾分鐘等。在一些情況下,除了讓處在開狀態的斷路器返回失敗和異常,也可以將其配置為返回一個對應用有意義的默認值。
問題和注意事項當考慮如何實現該模式時,需要考慮如下問題:
異常處理。應用通過斷路器調用服務需準備好如何處理因服務無法訪問而產生的異常。處理異常的方式因應用不同而不同。例如,應用應能臨時降級它對應的功能,調用候選的能獲得同樣數據的應用,或向用戶報告錯誤,請其過后重試。
異常的類型。請求可能由于各種原因而失敗,其中一些導致的問題可能比其他更嚴重。例如,請求可能由于外部服務宕機而失敗從而中斷數分鐘,或者由于服務過載而導致超時。斷路器可能能夠檢測異常的類型從而使用不同的策略。例如,如果要把斷路器設定到 開狀態,超時類型到錯誤次數的閾值要比系統完全不可用的閾值要高很懂。
日志。斷路器應該記錄所有失敗的請求(如何可以,也可以記錄成功的)來允許管理員來監控操作的健康狀況。
可恢復性。你應該為斷路器配置其保護的操作可能的恢復模型。例如,如果斷路器在打開狀態維持了很長時間,可能導致即使錯誤已經修復,斷路器仍拋出異常。類似的,如果斷路器從開到半開的時間太短,可能導致它上下波動,減少應用的響應時間(??沒懂)。
測試失敗的操作。在開的狀態下,除了用計數器來決定何時切換到半開狀態,斷路器還可以啟用一個定時任務來周期性 ping 遠端服務來判斷該服務是否已可以訪問。可以采用嘗試調用之前失敗的服務的形式,或調用遠端服務提供的專門用來測試服務狀態的操作,如健康狀況健康模式 所描述的那樣。
手動重載。對于系統恢復時間波動非常大的系統,提供一個手動重置選項來方便管理員關閉斷路器(同時重置失敗計數器)是很有用的。類似的,如果斷路器所保護的服務臨時不可用,管理員可以強制打開斷路器將其置為開狀態(同時重置計時器)。
并發。斷路器可能同時被大量客戶端訪問。其實現不用阻塞并發的請求,也不能給操作添加過多的額外負載。
資源區分。當我們為一個由多個獨立的提供者提供的同一個資源使用斷路器時,我們需要額外注意。例如,在一個由多個分片的數據存儲資源中,即便其他分片遇到臨時錯誤,單個分片也可以接受完全的訪問。如果在這種場景中,這些錯誤被合并成同一錯誤,應用可能會在某些分片錯誤時嘗試去訪問其他分片,但由于斷路器的存在,對其他分片的訪問也可能會被阻塞,即使它們可能成功。
加速熔斷。有時候返回的錯誤信息包含足夠信息令斷路器斷路。例如,一個共享資源過載,可直接另斷路器斷路而避免應用馬上重試。
[!注意事項]
一個服務可能在限流時返回 HTTP 429(太多的請求),或者在服務當前不可用時返回 HTTP 503(服務不可用)。HTTP 返回信息中可能包含了額外信息,比如下次重試的間隔時間等。
重放失敗的請求。在打開的狀態下,除了直接返回失敗,斷路器也可以將每個請求的詳細信息記錄到日志中,然后然后在遠程資源可訪問后,重放該請求。
外部服務不適合的超時。斷路器不適合用來保護那些設置了過長超時時長的外部服務。如果超時時間過長,斷路器的線程可能阻塞,在這段時間內,其他應用可能耶嘗試調用這個服務,從而導致斷路器消耗大量的線程。
在以下場景可以使用該模式:
阻止應用訪問一個很可能失敗的共享的遠程服務或資源。
以下場景不應該用該模式:
訪問本地資源,比如內存中的數據結構。在這個場景中,使用斷路器將為你的系統帶來額外的開銷。
用來替代業務邏輯中的異常處理。
例子在 web 應用中,頁面是根據外部服務獲得的數據計算生成的。如果系統設定較少的緩存策略,大多數頁面點擊都會調用一次服務。從 web 應用到服務的請求可以設定超時時間(通常是60秒),如果服務在這段時間內未響應,頁面的邏輯將認為服務不可用并拋出異常。
然而,如果服務失敗并且系統非常繁忙,用戶可能需要等60秒才會被提示異常。最終內存,鏈接,線程等資源可能會用盡,阻止其他用戶連接系統,即使它們并不是訪問失敗的那個服務。
通過添加更多的網絡服務器和實現負載均衡來為系統擴容能夠延緩資源耗盡的時間,但這并不會解決這個問題因為用戶的請求仍會未響應并且最終所以網絡服務器的資源終會耗盡。
為訪問該服務查詢數據的連接包裹一層斷路器能夠解決該問題,并且能更優雅地解決服務失敗。用戶的請求仍會失敗,但失敗將會更迅速并且不會阻塞資源。
The CircuitBreaker class maintains state information about a circuit breaker in an object that implements the ICircuitBreakerStateStore interface shown in the following code.
interface ICircuitBreakerStateStore { CircuitBreakerStateEnum State { get; } Exception LastException { get; } DateTime LastStateChangedDateUtc { get; } void Trip(Exception ex); void Reset(); void HalfOpen(); bool IsClosed { get; } }
The State property indicates the current state of the circuit breaker, and will be either Open, HalfOpen, or Closed as defined by the CircuitBreakerStateEnum enumeration. The IsClosed property should be true if the circuit breaker is closed, but false if it"s open or half open. The Trip method switches the state of the circuit breaker to the open state and records the exception that caused the change in state, together with the date and time that the exception occurred. The LastException and the LastStateChangedDateUtc properties return this information. The Reset method closes the circuit breaker, and the HalfOpen method sets the circuit breaker to half open.
The InMemoryCircuitBreakerStateStore class in the example contains an implementation of the ICircuitBreakerStateStore interface. The CircuitBreaker class creates an instance of this class to hold the state of the circuit breaker.
The ExecuteAction method in the CircuitBreaker class wraps an operation, specified as an Action delegate. If the circuit breaker is closed, ExecuteAction invokes the Action delegate. If the operation fails, an exception handler calls TrackException, which sets the circuit breaker state to open. The following code example highlights this flow.
public class CircuitBreaker { private readonly ICircuitBreakerStateStore stateStore = CircuitBreakerStateStoreFactory.GetCircuitBreakerStateStore(); private readonly object halfOpenSyncObject = new object (); ... public bool IsClosed { get { return stateStore.IsClosed; } } public bool IsOpen { get { return !IsClosed; } } public void ExecuteAction(Action action) { ... if (IsOpen) { // The circuit breaker is Open. ... (see code sample below for details) } // The circuit breaker is Closed, execute the action. try { action(); } catch (Exception ex) { // If an exception still occurs here, simply // retrip the breaker immediately. this.TrackException(ex); // Throw the exception so that the caller can tell // the type of exception that was thrown. throw; } } private void TrackException(Exception ex) { // For simplicity in this example, open the circuit breaker on the first exception. // In reality this would be more complex. A certain type of exception, such as one // that indicates a service is offline, might trip the circuit breaker immediately. // Alternatively it might count exceptions locally or across multiple instances and // use this value over time, or the exception/success ratio based on the exception // types, to open the circuit breaker. this.stateStore.Trip(ex); } }
The following example shows the code (omitted from the previous example) that is executed if the circuit breaker isn"t closed. It first checks if the circuit breaker has been open for a period longer than the time specified by the local OpenToHalfOpenWaitTime field in the CircuitBreaker class. If this is the case, the ExecuteAction method sets the circuit breaker to half open, then tries to perform the operation specified by the Action delegate.
If the operation is successful, the circuit breaker is reset to the closed state. If the operation fails, it is tripped back to the open state and the time the exception occurred is updated so that the circuit breaker will wait for a further period before trying to perform the operation again.
If the circuit breaker has only been open for a short time, less than the OpenToHalfOpenWaitTime value, the ExecuteAction method simply throws a CircuitBreakerOpenException exception and returns the error that caused the circuit breaker to transition to the open state.
Additionally, it uses a lock to prevent the circuit breaker from trying to perform concurrent calls to the operation while it"s half open. A concurrent attempt to invoke the operation will be handled as if the circuit breaker was open, and it"ll fail with an exception as described later.
... if (IsOpen) { // The circuit breaker is Open. Check if the Open timeout has expired. // If it has, set the state to HalfOpen. Another approach might be to // check for the HalfOpen state that had be set by some other operation. if (stateStore.LastStateChangedDateUtc + OpenToHalfOpenWaitTime < DateTime.UtcNow) { // The Open timeout has expired. Allow one operation to execute. Note that, in // this example, the circuit breaker is set to HalfOpen after being // in the Open state for some period of time. An alternative would be to set // this using some other approach such as a timer, test method, manually, and // so on, and check the state here to determine how to handle execution // of the action. // Limit the number of threads to be executed when the breaker is HalfOpen. // An alternative would be to use a more complex approach to determine which // threads or how many are allowed to execute, or to execute a simple test // method instead. bool lockTaken = false; try { Monitor.TryEnter(halfOpenSyncObject, ref lockTaken); if (lockTaken) { // Set the circuit breaker state to HalfOpen. stateStore.HalfOpen(); // Attempt the operation. action(); // If this action succeeds, reset the state and allow other operations. // In reality, instead of immediately returning to the Closed state, a counter // here would record the number of successful operations and return the // circuit breaker to the Closed state only after a specified number succeed. this.stateStore.Reset(); return; } catch (Exception ex) { // If there"s still an exception, trip the breaker again immediately. this.stateStore.Trip(ex); // Throw the exception so that the caller knows which exception occurred. throw; } finally { if (lockTaken) { Monitor.Exit(halfOpenSyncObject); } } } } // The Open timeout hasn"t yet expired. Throw a CircuitBreakerOpen exception to // inform the caller that the call was not actually attempted, // and return the most recent exception received. throw new CircuitBreakerOpenException(stateStore.LastException); } ...
To use a CircuitBreaker object to protect an operation, an application creates an instance of the CircuitBreaker class and invokes the ExecuteAction method, specifying the operation to be performed as the parameter. The application should be prepared to catch the CircuitBreakerOpenException exception if the operation fails because the circuit breaker is open. The following code shows an example:
var breaker = new CircuitBreaker(); try { breaker.ExecuteAction(() => { // Operation protected by the circuit breaker. ... }); } catch (CircuitBreakerOpenException ex) { // Perform some different action when the breaker is open. // Last exception details are in the inner exception. ... } catch (Exception ex) { ... }相關模式與指導
在實現該模式時,以下模式也會有幫助:
重試模式。
當應用嘗試連接服務或網絡資源時遇到臨時性錯誤時,簡單的重試之前失敗的操作。
健康狀態監控模式。
斷路器應該能夠通過給服務端點發送請求來測試服務的健康狀況。服務應該能夠返回信息來表明自身狀況。
翻譯自 Azure Cloud Design Patterns
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/25245.html
摘要:讀了微軟總結的云計算設計模式系列文章,覺得很受啟發,遂將這個系列翻譯出來如下。每個模式都描述了該模式試圖解決的問題,在使用該模式時應考慮的問題,以及一個基于微軟云的例子。 讀了微軟 Azure 總結的云計算設計模式系列文章,覺得很受啟發,遂將這個系列翻譯出來如下。 云計算模型 這些設計模式對于在云上構建高可用性,伸縮性,安全的應用程序很有用。每個模式都描述了該模式試圖解決的問題,在使用...
摘要:讀了微軟總結的云計算設計模式系列文章,覺得很受啟發,遂將這個系列翻譯出來如下。每個模式都描述了該模式試圖解決的問題,在使用該模式時應考慮的問題,以及一個基于微軟云的例子。 讀了微軟 Azure 總結的云計算設計模式系列文章,覺得很受啟發,遂將這個系列翻譯出來如下。 云計算模型 這些設計模式對于在云上構建高可用性,伸縮性,安全的應用程序很有用。每個模式都描述了該模式試圖解決的問題,在使用...
摘要:而微服務架構能否成功實踐,利用各種工具解決潛在問題是關鍵。因此,微服務本身可以通過庫和運行時代理解決客戶端服務發現負載均衡配置更新統計跟蹤等。與相比,解決了更廣的微服務架構問題。和處理了不同范圍的微服務架構技術點,而且是用了不同的方法。 Spring Cloud vs. Kubernetes,誰才是部署微服務的最佳拍檔? Spring Cloud和Kubernetes都聲稱自己是開發和...
摘要:是一個相對比較新的微服務框架,年才推出的版本雖然時間最短但是相比等框架提供的全套的分布式系統解決方案。提供線程池不同的服務走不同的線程池,實現了不同服務調用的隔離,避免了服務器雪崩的問題。通過互相注冊的方式來進行消息同步和保證高可用。 Spring Cloud 是一個相對比較新的微服務框架,...
摘要:日,公布了事故分析。此外,亞馬遜還完成了對所有備用配電的審計。至此,亞馬遜表示,已經確定所有斷路器都是正確的配置了,并會進行定期的測試和審計。最后,亞馬遜對在這次事件中受到損失的企業表示了歉意。 上周四即6月14日,Amazon位于美國東部的數據中心出現故障,并影響了AWS多項云服務以及基于之上的Heroku、Quora等知名網站。16日,Amaozn公布了事故分析。事故是由公共電網故障引...
閱讀 2735·2021-11-22 15:22
閱讀 1643·2021-11-22 14:56
閱讀 3620·2021-09-22 15:12
閱讀 2408·2021-09-02 15:41
閱讀 2128·2021-08-27 16:26
閱讀 1118·2019-08-30 15:55
閱讀 2144·2019-08-29 17:30
閱讀 672·2019-08-29 16:26