日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

服务容错模式

發布時間:2025/4/5 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 服务容错模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

https://tech.meituan.com/service-fault-tolerant-pattern.html

背景

隨著美團點評服務框架和服務治理體系的逐步成熟,服務化已成為公司內部系統設計的趨勢。本著大系統小做、職責單一的原則,我們度假技術團隊對業務系統進行了不少服務化拆分工作。隨著業務復雜度的增加,依賴的服務也逐步增加,出現了不少由于服務調用出現異常問題而導致的重大事故,如:

1)系統依賴的某個服務發生延遲或者故障,數秒內導致所有應用資源(線程,隊列等)被耗盡,造成所謂的雪崩效應 (Cascading Failure),導致整個系統拒絕對外提供服務。

2)系統遭受惡意爬蟲襲擊,在放大效應下沒有對下游依賴服務做好限速處理,最終導致下游服務崩潰。

容錯是一個很大的話題,受篇幅所限,本文將介紹僅限定在服務調用間常用的一些容錯模式。

設計原則

服務容錯的設計有個基本原則,就是“Design for Failure”。為了避免出現“千里之堤潰于蟻穴”這種情況,在設計上需要考慮到各種邊界場景和對于服務間調用出現的異常或延遲情況,同時在設計和編程時也要考慮周到。這一切都是為了達到以下目標:

1)一個依賴服務的故障不會嚴重破壞用戶的體驗。

2)系統能自動或半自動處理故障,具備自我恢復能力。

基于這個原則和目標,衍生出下文將要介紹的一些模式,能夠解決分布式服務調用中的一些問題,提高系統在故障發生時的存活能力。

一些經典的容錯模式

所謂模式,其實就是某種場景下一類問題及其解決方案的總結歸納,往往可以重用。模式可以指導我們完成任務,作出合理的系統設計方案,達到事半功倍的效果。而在服務容錯這個方向,行業內已經有了不少實踐總結出來的解決方案。

超時與重試(Timeout and Retry)

超時模式,是一種最常見的容錯模式,在美團點評的工程實踐中大量存在。常見的有設置網絡連接超時時間,一次RPC的響應超時時間等。在分布式服務調用的場景中,它主要解決了當依賴服務出現建立網絡連接或響應延遲,不用無限等待的問題,調用方可以根據事先設計的超時時間中斷調用,及時釋放關鍵資源,如Web容器的連接數,數據庫連接數等,避免整個系統資源耗盡出現拒絕對外提供服務這種情況。

重試模式,一般和超時模式結合使用,適用于對于下游服務的數據強依賴的場景(不強依賴的場景不建議使用!),通過重試來保證數據的可靠性或一致性,常用于因網絡抖動等導致服務調用出現超時的場景。與超時時間設置結合使用后,需要考慮接口的響應時間分布情況,超時時間可以設置為依賴服務接口99.5%響應時間的值,重試次數一般1-2次為宜,否則會導致請求響應時間延長,拖累到整個系統。

一些實現說明:

public class RetryCommand<T> {private int maxRetries = 2;// 重試次數 默認2次private long retryInterval = 5;//重試間隔時間ms 默認5msprivate Map<String, Object> params;public RetryCommand() {}public RetryCommand(long retryInterval, int maxRetries) {this.retryInterval = retryInterval;this.maxRetries = maxRetries;}public T command(Map<String, Object> params){//Some remote service call with timeoutserviceA.doSomethingWithTimeOut(timeout);}private final T retry() throws RuntimeException {int retryCounter = 0;while (retryCounter < maxRetries) {try {return command(params);} catch (Exception e) {retryCounter++;if (retryCounter >= maxRetries) {break;}}}throw new RuntimeException("Command failed on all of " + maxRetries + " retries"); }//省略 }

限流(Rate Limiting/Load Shedder)

限流模式,常用于下游服務容量有限,但又怕出現突發流量猛增(如惡意爬蟲,節假日大促等)而導致下游服務因壓力過大而拒絕服務的場景。常見的限流模式有控制并發和控制速率,一個是限制并發的數量,一個是限制并發訪問的速率。

控制并發

屬于一種較常見的限流手段,在工程實踐中可以通過信號量機制(如Java中的Semaphore)來控制,舉個例子:

假如有一個需求,要讀取幾萬個文件的數據,因為都是IO密集型任務,我們可以啟動幾十個線程并發的讀取,但是如果讀到內存后,還需要存儲到數據庫中,而數據庫的連接數只有10個,這時我們必須控制只有十個線程同時獲取數據庫連接保存數據,否則會報錯無法獲取數據庫連接。這個時候,我們就可以使用Semaphore來控制并發數,如:

public class SemaphoreTest {private static final int THREAD_COUNT = 30;private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);private static Semaphore s = new Semaphore(10);public static void main(String[] args) {for (int i = 0; i < THREAD_COUNT; i++) {threadPool.execute(new Runnable() {@Overridepublic void run() {try {s.acquire();System.out.println("save data");s.release();} catch (InterruptedException e) {e.printStack();}}});}threadPool.shutdown();} }

在代碼中,雖然有30個線程在執行,但是只允許10個并發的執行。Semaphore的構造方法Semaphore(int permits) 接受一個整型的數字,表示可用的許可證數量。Semaphore(10)表示允許10個線程獲取許可證,也就是最大并發數是10。Semaphore的用法也很簡單,首先線程使用Semaphore的acquire()獲取一個許可證,使用完之后調用release()歸還許可證,還可以用tryAcquire()方法嘗試獲取許可證。

控制速率

在我們的工程實踐中,常見的是使用令牌桶算法來實現這種模式,其他如漏桶算法也可以實現控制速率,但在我們的工程實踐中使用不多,這里不做介紹,讀者請自行了解。

在Wikipedia上,令牌桶算法是這么描述的:

  • 每秒會有r個令牌放入桶中,或者說,每過1/r秒桶中增加一個令牌。

  • 桶中最多存放b個令牌,如果桶滿了,新放入的令牌會被丟棄。

  • 當一個n字節的數據包到達時,消耗n個令牌,然后發送該數據包。

  • 如果桶中可用令牌小于n,則該數據包將被緩存或丟棄。

  • 令牌桶控制的是一個時間窗口內通過的數據量,在API層面我們常說的QPS、TPS,正好是一個時間窗口內的請求量或者事務量,只不過時間窗口限定在1s罷了。以一個恒定的速度往桶里放入令牌,而如果請求需要被處理,則需要先從桶里獲取一個令牌,當桶里沒有令牌可取時,則拒絕服務。令牌桶的另外一個好處是可以方便的改變速度,一旦需要提高速率,則按需提高放入桶中的令牌的速率。

    在我們的工程實踐中,通常使用Guava中的Ratelimiter來實現控制速率,如我們不希望每秒的任務提交超過兩個:

    //速率是每秒兩個許可 final RateLimiter rateLimiter = RateLimiter.create(2.0);void submitTasks(List tasks, Executor executor) {for (Runnable task : tasks) {rateLimiter.acquire(); // 也許需要等待executor.execute(task);} }

    電路熔斷器(Circuit Breaker)

    在我們的工程實踐中,偶爾會遇到一些服務由于網絡連接超時,系統有異常或load過高出現暫時不可用等情況,導致對這些服務的調用失敗,可能需要一段時間才能修復,這種對請求的阻塞可能會占用寶貴的系統資源,如:內存,線程,數據庫連接等等,最壞的情況下會導致這些資源被消耗殆盡,使得系統里不相關的部分所使用的資源也耗盡從而拖累整個系統。在這種情況下,調用操作能夠立即返回錯誤而不是等待超時的發生或者重試可能是一種更好的選擇,只有當被調用的服務有可能成功時我們再去嘗試。

    熔斷器模式可以防止我們的系統不斷地嘗試執行可能會失敗的調用,使得我們的系統繼續執行而不用等待修正錯誤,或者浪費CPU時間去等到長時間的超時產生。熔斷器模式也可以使我們系統能夠檢測錯誤是否已經修正,如果已經修正,系統會再次嘗試調用操作。下圖是個使用熔斷器模式的調用流程:

    可以從圖中看出,當超時出現的次數達到一定條件后,熔斷器會觸發打開狀態,客戶端的下次調用將直接返回,不用等待超時產生。

    在熔斷器內部,往往有以下幾種狀態:

    1)閉合(closed)狀態:該狀態下能夠對目標服務或方法進行正常的調用。熔斷器類維護了一個時間窗口內調用失敗的次數,如果某次調用失敗,則失敗次數加1。如果最近失敗次數超過了在給定的時間窗口內允許失敗的閾值(可以是數量也可以是比例),則熔斷器類切換到斷開(Open)狀態。此時熔斷器設置了一個計時器,當時鐘超過了該時間,則切換到半斷開(Half-Open)狀態,該睡眠時間的設定是給了系統一次機會來修正導致調用失敗的錯誤。

    2)斷開(Open)狀態:在該狀態下,對目標服務或方法的請求會立即返回錯誤響應,如果設置了fallback方法,則會進入fallback的流程。

    3)半斷開(Half-Open)狀態:允許對目標服務或方法的一定數量的請求可以去調用服務。如果這些請求對服務的調用成功,那么可以認為之前導致調用失敗的錯誤已經修正,此時熔斷器切換到閉合狀態(并且將錯誤計數器重置);如果這一定數量的請求有調用失敗的情況,則認為導致之前調用失敗的問題仍然存在,熔斷器切回到斷開方式,然后開始重置計時器來給系統一定的時間來修正錯誤。半斷開狀態能夠有效防止正在恢復中的服務被突然而來的大量請求再次拖垮。

    在我們的工程實踐中,熔斷器模式往往應用于服務的自動降級,在實現上主要基于Netflix開源的組件Hystrix來實現,下圖和代碼分別是Hystrix中熔斷器的原理和定義,更多了解可以查看Hystrix的源碼:

    public interface HystrixCircuitBreaker {/*** Every {@link HystrixCommand} requests asks this if it is allowed to proceed or not.* <p>* This takes into account the half-open logic which allows some requests through when determining if it should be closed again.** @return boolean whether a request should be permitted*/public boolean allowRequest();/*** Whether the circuit is currently open (tripped).** @return boolean state of circuit breaker*/public boolean isOpen();/*** Invoked on successful executions from {@link HystrixCommand} as part of feedback mechanism when in a half-open state.*/public void markSuccess(); }

    艙壁隔離(Bulkhead Isolation)

    在造船行業,往往使用此類模式對船艙進行隔離,利用艙壁將不同的船艙隔離起來,這樣如果一個船艙破了進水,只損失一個船艙,其它船艙可以不受影響,而借鑒造船行業的經驗,這種模式也在軟件行業得到使用。

    線程隔離(Thread Isolation)就是這種模式的常見的一個場景。例如,系統A調用了ServiceB/ServiceC/ServiceD三個遠程服務,且部署A的容器一共有120個工作線程,采用線程隔離機制,可以給對ServiceB/ServiceC/ServiceD的調用各分配40個線程。當ServiceB慢了,給ServiceB分配的40個線程因慢而阻塞并最終耗盡,線程隔離可以保證給ServiceC/ServiceD分配的80個線程可以不受影響。如果沒有這種隔離機制,當ServiceB慢的時候,120個工作線程會很快全部被對ServiceB的調用吃光,整個系統會全部慢下來,甚至出現系統停止響應的情況。

    這種Case在我們實踐中經常遇到,如某接口由于數據庫慢查詢,外部RPC調用超時導致整個系統的線程數過高,連接數耗盡等。我們可以使用艙壁隔離模式,為這種依賴服務調用維護一個小的線程池,當一個依賴服務由于響應慢導致線程池任務滿的時候,不會影響到其他依賴服務的調用,它的缺點就是會增加線程數。

    無論是超時/重試,熔斷器,還是艙壁隔離模式,它們在使用過程中都會出現異常情況,異常情況的處理方式間接影響到用戶的體驗,針對異常情況的處理也有一種模式支撐,這就是回退(fallback)模式。

    回退(Fallback)

    在超時,重試失敗,熔斷或者限流發生的時候,為了及時恢復服務或者不影響到用戶體驗,需要提供回退的機制,常見的回退策略有:

  • 自定義處理:在這種場景下,可以使用默認數據,本地數據,緩存數據來臨時支撐,也可以將請求放入隊列,或者使用備用服務獲取數據等,適用于業務的關鍵流程與嚴重影響用戶體驗的場景,如商家/產品信息等核心服務。

  • 故障沉默(fail-silent):直接返回空值或缺省值,適用于可降級功能的場景,如產品推薦之類的功能,數據為空也不太影響用戶體驗。

  • 快速失敗(fail-fast):直接拋出異常,適用于數據非強依賴的場景,如非核心服務超時的處理。

  • 應用實例

    在實際的工程實踐中,這四種模式既可以單獨使用,也可以組合使用,為了讓讀者更好的理解這些模式的應用,下面以Netflix的開源組件Hystrix的流程為例說明。

    圖中流程的說明:

  • 將遠程服務調用邏輯封裝進一個HystrixCommand。

  • 對于每次服務調用可以使用同步或異步機制,對應執行execute()或queue()。

  • 判斷熔斷器(circuit-breaker)是否打開或者半打開狀態,如果打開跳到步驟8,進行回退策略,如果關閉進入步驟4。

  • 判斷線程池/隊列/信號量(使用了艙壁隔離模式)是否跑滿,如果跑滿進入回退步驟8,否則繼續后續步驟5。

  • run方法中執行了實際的服務調用。

    a. 服務調用發生超時時,進入步驟8。

  • 判斷run方法中的代碼是否執行成功。

    a. 執行成功返回結果。

    b. 執行中出現錯誤則進入步驟8。

  • 所有的運行狀態(成功,失敗,拒絕,超時)上報給熔斷器,用于統計從而影響熔斷器狀態。

  • 進入getFallback()回退邏輯。

    a. 沒有實現getFallback()回退邏輯的調用將直接拋出異常。

    b. 回退邏輯調用成功直接返回。

    c. 回退邏輯調用失敗拋出異常。

  • 返回執行成功結果。

  • 總結

    服務容錯模式在美團點評系統的穩定性保障方面應用很多,學習模式有助于新人直接利用熟練軟件工程師的經驗,對于提升系統的穩定性有很大的幫助。服務容錯的目的主要是為了防微杜漸,除此之外錯誤的及時發現和監控其實同等重要。隨著技術的演化,新的模式在不斷的學習與實踐中沉淀出來,美團點評度假技術團隊在構建一個高可用高性能的系統目標之外,讓系統越來越有彈性(Resilience)也是我們新的追求。

    參考文獻

  • Netflix Hystrix Wiki

  • Martin Fowler.?CircuitBreaker

  • Hanmer R. Patterns for Fault Tolerant Software. Wiley, 2007.

  • Nygard M. 發布!軟件的設計與部署. 凃鳴 譯. 人民郵電出版社, 2015.

  • 轉載于:https://www.cnblogs.com/davidwang456/articles/9271897.html

    《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀

    總結

    以上是生活随笔為你收集整理的服务容错模式的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。