10 張圖帶你徹底搞懂限流、熔斷、服務降級

作者 | jinjunzhu       責編 | 歐陽姝黎

在分佈式系統中,如果某個服務節點發生故障或者網絡發生異常,都有可能導致調用方被阻塞等待,如果超時時間設置很長,調用方資源很可能被耗盡。這又導致了調用方的上游系統發生資源耗盡的情況,最終導致系統雪崩。

如下圖:如果 D 服務發生了故障不能響應,B 服務調用 D 時只能阻塞等待。假如 B 服務調用 D 服務設置超時時間是 10 秒,請求速率是每秒 100 個,那 10 秒內就會有 1000 個請求線程被阻塞等待,如果 B 的線程池大小設置 1000,那 B 系統因爲線程資源耗盡已經不能對外提供服務了。而這又影響了入口系統 A 的服務,最終導致系統全面崩潰。

提高系統的整體容錯能力是防止系統雪崩的有效手段。

在 Martin Fowler 和 James Lewis 的文章 《Microservices: a definition of this new architectural term》[1] 中,提出了微服務的 9 個特徵,其中一個是容錯設計。

要防止系統發生雪崩,就必須要有容錯設計。如果遇到突增流量,一般的做法是對非核心業務功能採用熔斷和服務降級的措施來保護核心業務功能正常服務,而對於核心功能服務,則需要採用限流的措施。

今天我們來聊一聊系統容錯中的限流、熔斷和服務降級。

限流

當系統的處理能力不能應對外部請求的突增流量時,爲了不讓系統奔潰,必須採取限流的措施。

1.1 限流指標

1.1.1 TPS

系統吞吐量是衡量系統性能的關鍵指標,按照事務的完成數量來限流是最合理的。

但是對實操性來說,按照事務來限流並不現實。在分佈式系統中完成一筆事務需要多個系統的配合。比如我們在電商系統購物,需要訂單、庫存、賬戶、支付等多個服務配合完成,有的服務需要異步返回,這樣完成一筆事務花費的時間可能會很長。如果按照 TPS 來進行限流,時間粒度可能會很大大,很難準確評估系統的響應性能。

1.1.2 HPS

每秒請求數,指每秒鐘服務端收到客戶端的請求數量。

如果一個請求完成一筆事務,那 TPS 和 HPS 是等同的。但在分佈式場景下,完成一筆事務可能需要多次請求,所以 TPS 和 HPS 指標不能等同看待。

1.1.3 QPS

服務端每秒能夠響應的客戶端查詢請求數量。

如果後臺只有一臺服務器,那 HPS 和 QPS 是等同的。但是在分佈式場景下,每個請求需要多個服務器配合完成響應。

目前主流的限流方法多采用 HPS 作爲限流指標。

1.2 限流方法

1.2.1 流量計數器

這是最簡單直接的方法,比如限制每秒請求數量 100,超過 100 的請求就拒絕掉。

但是這個方法存在 2 個明顯的問題:

這張圖上,從下面時間看,HPS 沒有超過 100,但是從上面看 HPS 超過 100 了。

有一段時間流量超了,也不一定真的需要限流,如下圖,系統 HPS 限制 50,雖然前 3s 流量超了,但是如果讀超時時間設置爲 5s,並不需要限流。

1.2.2 滑動時間窗口

滑動時間窗口算法是目前比較流行的限流算法,主要思想是把時間看做是一個向前滾動的窗口,如下圖:開始的時候,我們把 t1~t5 看做一個時間窗口,每個窗口 1s,如果我們定的限流目標是每秒 50 個請求,那 t1~t5 這個窗口的請求總和不能超過 250 個。

這個窗口是滑動的,下一秒的窗口成了 t2~t6,這時把 t1 時間片的統計拋棄,加入 t6 時間片進行統計。這段時間內的請求數量也不能超過 250 個。

滑動時間窗口的優點是解決了流量計數器算法的缺陷,但是也有 2 個問題:

1.2.3 漏桶算法

漏桶算法的思想如下圖:在客戶端的請求發送到服務器之前,先用漏桶緩存起來,這個漏桶可以是一個長度固定的隊列,這個隊列中的請求均勻的發送到服務端。

如果客戶端的請求速率太快,漏桶的隊列滿了,就會被拒絕掉,或者走降級處理邏輯。這樣服務端就不會受到突發流量的衝擊。

漏桶算法的優點是實現簡單,可以使用消息隊列來削峯填谷。

但是也有 3 個問題需要考慮:

漏桶大小和發送速率這 2 個值在項目上線初期都會根據測試結果選擇一個值,但是隨着架構的改進和集羣的伸縮,這 2 個值也會隨之發生改變。

1.2.4 令牌桶算法

令牌桶算法就跟病人去醫院看病一樣,找醫生之前需要先掛號,而醫院每天放的號是有限的。當天的號用完了,第二天又會放一批號。

算法的基本思想就是週期性的執行下面的流程:客戶端在發送請求時,都需要先從令牌桶中獲取令牌,如果取到了,就可以把請求發送給服務端,取不到令牌,就只能被拒絕或者走服務降級的邏輯。如下圖:

令牌桶算法解決了漏桶算法的問題,而且實現並不複雜,使用信號量就可以實現。在實際限流場景中使用最多,比如 google 的 guava 中就實現了令牌桶算法限流,感興趣可以研究一下。

1.2.5 分佈式限流

如果在分佈式系統場景下,上面介紹的 4 種限流算法是否還適用呢?

以令牌桶算法爲例,假如在電商系統中客戶下了一筆訂單,如下圖:如果我們把令牌桶單獨保存在一個地方 (比如 redis 中) 供整個分佈式系統用,那客戶端在調用組合服務,組合服務調用訂單、庫存和賬戶服務都需要跟令牌桶交互,交互次數明顯增加了很多。

有一種改進就是客戶端調用組合服務之前首先獲取四個令牌,調用組合服務時減去一個令牌並且傳遞給組合服務三個令牌,組合服務調用下面三個服務時依次消耗一個令牌。

1.2.6 hystrix 限流

hystrix 可以使用信號量和線程池來進行限流。

1.2.6.1 信號量限流

hystrix 可以使用信號量進行限流,比如在提供服務的方法上加下面的註解。這樣只能有 20 個併發線程來訪問這個方法,超過的就被轉到了 errMethod 這個降級方法。

1@HystrixCommand(
2 commandProperties= {
3   @HystrixProperty(),
4   @HystrixProperty()
5 },
6 fallbackMethod = "errMethod"
7)
8
9

1.2.6.2 線程池限流

hystrix 也可以使用線程池進行限流,在提供服務的方法上加下面的註解,當線程數量

 1@HystrixCommand(
 2    commandProperties = {
 3            @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD")
 4    },
 5    threadPoolKey = "createOrderThreadPool",
 6    threadPoolProperties = {
 7            @HystrixProperty(name = "coreSize", value = "20"),
 8   @HystrixProperty(name = "maxQueueSize", value = "100"),
 9            @HystrixProperty(name = "maximumSize", value = "30"),
10            @HystrixProperty(name = "queueSizeRejectionThreshold", value = "120")
11    },
12    fallbackMethod = "errMethod"
13)
14
15

這裏要注意:在 java 的線程池中,如果線程數量超過 coreSize,創建線程請求會優先進入隊列,如果隊列滿了,就會繼續創建線程直到線程數量達到 maximumSize,之後走拒絕策略。但在 hystrix 配置的線程池中多了一個參數 queueSizeRejectionThreshold,如果 queueSizeRejectionThreshold <maxQueueSize, 隊列數量達到 queueSizeRejectionThreshold 就會走拒絕策略了,因此 maximumSize 失效了。如果 queueSizeRejectionThreshold> maxQueueSize, 隊列數量達到 maxQueueSize 時,maximumSize 是有效的,系統會繼續創建線程直到數量達到 maximumSize。Hytrix 線程池設置坑 [2]

熔斷

相信大家對斷路器並不陌生,它就相當於一個開關,打開後可以阻止流量通過。比如保險絲,當電流過大時,就會熔斷,從而避免元器件損壞。

服務熔斷是指調用方訪問服務時通過斷路器做代理進行訪問,斷路器會持續觀察服務返回的成功、失敗的狀態,當失敗超過設置的閾值時斷路器打開,請求就不能真正地訪問到服務了。

爲了更好地理解,我畫了下面的時序圖:

可以參考 Martin Fowler 的論文《CircuitBreaker》[3]。

2.1 斷路器的狀態

斷路器有 3 種狀態:

斷路器的狀態切換圖如下:2.2 需要考慮的問題

使用斷路器需要考慮一些問題:

2.3 使用場景

服務降級

前面講了限流和熔斷,相比來說,服務降級是站在系統全局的視角來考慮的。

在服務發生熔斷後,一般會讓請求走事先配置的處理方法,這個處理方法就是一個降級邏輯。

服務降級是對非核心、非關鍵的服務進行降級。

3.1 使用場景

3.2 使用 hystrix 降級

3.2.1 異常降級

hystrix 降級時可以忽略某個異常,在方法上加上 @HystrixCommand 註解:

下面的代碼定義降級方法是 errMethod,對 ParamErrorException 和 BusinessTypeException 這兩個異常不做降級處理。

1@HystrixCommand(
2 fallbackMethod = "errMethod",
3 ignoreExceptions = {ParamErrorException.class, BusinessTypeException.class}
4)
5
6

3.2.2 調用超時降級

專門針對調用第三方接口超時降級。

下面的方法是調用第三方接口 3 秒未收到響應就降級到 errMethod 方法。

1@HystrixCommand(
2    commandProperties = {
3            @HystrixProperty(),
4            @HystrixProperty(),
5    },
6    fallbackMethod = "errMethod"
7)
8
9

總結


限流、熔斷和服務降級是系統容錯的重要設計模式,從一定意義上講限流和熔斷也是一種服務降級的手段。

熔斷和服務降級主要是針對非核心業務功能,而核心業務如果流程超過預估的峯值,就需要進行限流。

對於限流,選擇合理的限流算法很重要,令牌桶算法優勢很明顯,也是使用最多的限流算法。

在系統設計的時候,這些模式需要配合業務量的預估、性能測試的數據進行相應閾值的配置,而這些閾值最好保存在配置中心,方便實時修改。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YLBXAdNkWntDwe0KU6K0zQ