1. 程式人生 > >雲端計算設計模式翻譯(二):Circuit Breaker Pattern(斷路器模式)

雲端計算設計模式翻譯(二):Circuit Breaker Pattern(斷路器模式)

    當連線使用遠端服務或資源時,可能需要花不少精力來做好錯誤處理。這個模式可以有效提高程式的穩定性和彈性。

Context and Problem

    在像雲這種分散式的環境中,應用程式的操作經常訪問遠端的資源和服務。然而這類操作有可能因為網路響應慢、超時、資源暫時不可用等瞬時性故障(transient faults)而失敗。這些故障通常情況下會在一小段時間後自動恢復,而對於一個好的雲應用來說,必須要有一個好的策略(比如嘗試重試)來處理這種情況。

    但是有時候錯誤是由一些難以預期的異常事件導致的,這種情況就可能會花費相對來說較多的時間來處理和修復,並且直接導致部分服務甚至所有服務無法使用。當發生了這種情況時,應用程式再進行不停地重試就沒什麼意義了,此時應用程式應該儘快將本次操作(包括重試)標為徹底失敗,並在應用程式端觸發對應的錯誤處理邏輯。

    此外,對於一個訪問壓力很大的服務來說,系統中某部分的異常可能會引發一系列連鎖的異常。舉個栗子:比如說我們現在有個操作會去呼叫一個遠端服務,並且這個操作實現了超時機制,在指定的時間段內如果對方服務沒有響應那麼本次操作就會返回一個failure訊息。假設此時系統壓力很大,不停地有大量並行的請求都呼叫這個操作,如果此時遠端服務出現異常沒有響應了,那麼這些並行的例項都會被阻塞直到超時。再假設這些被阻塞的請求都擁有一些系統臨界資源(比如記憶體、執行緒、資料庫連線等等),那麼這種情況下這些資源會被很快消耗殆盡,這樣一來其他一些無關但也用到這些資源的操作也會失敗。在這種情況下,我們可能更希望這個操作能夠快速失敗,僅當服務有可能成功呼叫時再進行重試(而不是傻傻地來一個操作失敗了不停重試呼叫這個服務)。另外,將超時時間設短一點也許能解決這個問題,但也不能設得太短(為了應對瞬時錯誤)。

Solution

    斷路器模式能夠防止應用程式不停嘗試一個極有可能失敗的操作,使應用程式能夠繼續執行而不用花費大量時間來進行沒有必要的錯誤處理或等待異常恢復。另外,斷路器模式能夠使應用程式能夠檢測到異常是否已經被修復,如果對應異常已經被修復了,那麼應用程式就又可以嘗試來呼叫這個操作了。(斷路器模式和單純的重試區別非常大,具體比較在後面談到重試模式時再介紹。另外,可以結合現實中的斷路器的功能來理解這個模式)。

    一個斷路器相當於是一個可能失敗的操作的代理。這個代理會監控最近錯誤發生的次數,然後通過這些資訊來決定是讓一個應用程式來呼叫這個操作還是直接立即返回一個異常。這個代理可以用狀態機的形式來實現從而模擬電力電子學中斷路器的功能,主要有下面這些狀態:

Closed: 應用程式能夠直接通過這個代理來呼叫操作。在這個代理內部儲存了最近發生錯誤的次數計數器,如果發生了一次失敗的呼叫,那麼這個計數器就會增加;如果在一定的時間段內這個計數器的值超過了一個設定的閾值,那麼這個代理就會進入Open狀態。此時,代理會啟動一個超時計時器,當這個計時器到期時,代理就會進入Half-Open狀態(設定這個超時器的目的是:在允許應用程式再次呼叫這個操作之前,給系統一段時間來解決導致異常的錯誤)。

Open: 這種狀態下應用程式的呼叫請求會立即失敗並且收到一個exception。

Half-Open: 這種狀態下只允許限量呼叫這個操作。如果所有這些呼叫成功了,那麼就認為之前導致異常的錯誤被解決了,此時代理的狀態會被設定成Closed,同時重置錯誤計數器;如果這些呼叫中還存在錯誤的情況,那麼就認為這個問題還沒有被解決,此時代理的狀態被重新設定成Open,並重置超時計時器,從而再給系統一個時間來解決這個問題。一個剛剛恢復的服務可能並不能一下子支援大量的服務請求直到恢復流程徹底走完,否則過大的訪問壓力也許會導致正在出於恢復過程中的服務再次因為超時或失敗,而Half-Open狀態能夠防止一個剛剛恢復的服務一下子被請求淹沒(限量訪問,一旦有錯就進入Open停止對服務的訪問),從而安全地走完它的錯誤處理/恢復過程。


說明:

1.在Closed中的failure counter是基於時間的,每隔一段時間會自動清零。這是為了防止斷路器因為一些偶發的錯誤而進入Open狀態。所以總結一下就是從Closed狀態切到Open狀態需要讓錯誤計數器在一個時間段內達到閾值,否則較低頻率出現的錯誤不會導致進入Open狀態。

2.在Half-Open狀態中用到的success counter記錄的是成功呼叫的次數。當連續成功的次數達到一個設定的閾值後狀態就會切為Closed,在此期間一旦發生失敗的呼叫,狀態立即切為Open,success counter在下次狀態從Open切換到Half-Open時被重置。

    實現斷路器模式能夠提高系統的穩定性和彈性,並使系統在錯誤恢復過程中保持穩定,同時也將錯誤對系統性能的影響最小化。通過直接拒絕那些很有可能失敗(基本上是一定會失敗)的請求而不是不斷地無腦重試能夠保證系統響應時間的穩定(儘管可能響應的是一個報錯訊息,那也比因為錯誤重試導致請求堆積引發更大的問題來的好)。我們也可以在斷路器每次狀態切換時加上列印或訊息通知,這些日誌資訊可以有效幫助監聽系統健康狀態,或者在斷路器狀態變成Open時及時通知維護人員。

    在實際使用中可以根據實際情況對這個模式進行定製化。比如說你可以將Open狀態定時器實現成一個自增的定時器,即最開始超時時間僅為一個很短的時間比如幾秒,如果到期切成Half-Open狀態又被打回來了,就延長超時時間;再比如說Open狀態可以不返回一個異常而是返回一個預設值。

Issues and Considerations

當使用這一模式時需要注意以下幾點:

1.異常處理。通過斷路器來呼叫一個服務必須要對服務不可用的情況做好異常處理。不同的應用根據自己的需求來定製這個異常處理邏輯。

2.異常型別。一次請求失敗的理由五花八門,有些異常相比其他的可能會嚴重得多(也就是說需要更多時間來做錯誤處理)。斷路器可能需要針對不同的異常來配置不同策略(各種閾值根據不同的異常型別來進行配置)。

3.日誌記錄。斷路器對於每次異常情況都需要新增日誌記錄使維護人員能夠監測系統的監控狀況,並協助開發人員來進行改進優化。

4.可恢復性。在實際使用中設定斷路器的引數時應該考慮到其所保護操作的恢復特性。舉個例子,如果Open超時時間設定得遠遠超過異常恢復所需要的時間,那麼則極有可能出現斷路器長時間處於Open狀態,即使異常早已被解決,呼叫者也只會一直受到異常資訊。類似的,如果將這個時間設定得相對異常恢復時間來說過分短,那麼就會使斷路器頻繁地在Open狀態和Half-Open狀態之間跳動,從而使整個應用的響應時間變得非常不穩定。

5.對失敗的操作進行測試。舉個例子來說,在Open狀態下,我們除了使用一個超時計時器來作為Open狀態切換到Half-Open狀態的依據外,也可以嘗試週期地Ping遠端服務/資源的IP,以此為依據來判斷遠端服務/資源是否可用。這種方式實際上是用一種相對來說開銷更小的訪問模式來測試遠端服務的連通性和健康程度。

6.外部手動控制。在一個複雜系統之中,由於不同的異常恢復時間和異常處理流程都不盡相同,我們可以為系統管理員提供一個人工介入的介面來應對一些特殊的情況,比如可以提供重置計數器命令、重置超時器命令等,甚至也可以提供介面使管理員能夠手動更改斷路器的狀態。

7.併發性。同一個斷路器有可能同時被大量的請求使用,所以在設計和實現斷路器時不能出現阻塞的情景,也要儘可能地減少呼叫過程中的資源開銷。

8.資源差異性。當用一個斷路器來控制一類資源(雖然為同一類資源甚至同一個資源,但很有可能是由不同獨立的資源提供者組成)訪問時需要格外注意。舉個例子,一個數據儲存器可能是由多個不同的分片組成的,此時存在一種可能就是有些分片可能能夠正常的訪問,但與此同時另一些分片無法正常訪問。此時加在這個資料儲存之上的斷路器對於訪問出現的錯誤如果混在一起用同樣的處理,那麼呼叫者則很有可能會嘗試訪問那些無法正常訪問的分片。

9.對斷路器進行加速。有時候儘管斷路器沒有達到標準的切換條件,但根據響應中所攜帶的資訊已經可以確定要做狀態切換以及維持這個狀態的時間。比如如果訪問一個正在進行過載的資源時收到的響應已經告訴訪問端資源正在進行過載,可能需要幾分鐘,那麼此時斷路器在收到訊息後可以立刻切成Open狀態,並根據響應中包含的時間來配置超時時間。

10.呼叫重試。在Open狀態期間,除了簡單地對呼叫者返回一個錯誤或預設值外,也可以對這些被直接打回去的呼叫做記錄,當資源可用時對這些記錄中的呼叫請求進行重試。