徹底搞懂服務限流和服務降級
我們知道,在分布式系統(tǒng)中,當面對高并發(fā)、大流量的應用場景時,系統(tǒng)就會面臨崩潰的風險。這時候,為了提高系統(tǒng)可用性,我們就需要對外部流量進行限制,以減少對現(xiàn)有系統(tǒng)的影響。目前,業(yè)界主流的兩種最佳實踐是服務限流和服務降級,這節(jié)課我們就來深入了解這兩種解決方案。
假如你正在設計和開發(fā)一個分布式服務系統(tǒng),各個服務為了滿足特定業(yè)務需求,通常都會相互依賴形成一種復雜的調用鏈路。那么,在這樣的調用鏈路中,服務可能因為自身無法承受大量的請求而發(fā)生調用失敗,或者可能因為是該服務所依賴的其他服務出現(xiàn)問題而導致自身發(fā)生失敗,如下所示:
圖片
要應對這兩種失敗場景,我們有兩種核心的解決思路:如果是服務自身發(fā)生失敗,我們希望能通過合理控制對它的請求量,降低失敗的可能性,這是服務限流方案;如果是依賴的服務有問題而導致調用失敗,那么就希望別的服務訪問該服務時能夠快速返回錯誤,以免導致服務依賴所產(chǎn)生的“雪崩效益”,也就是服務降級方案。
那么下面,我們就先來了解下限流的具體實現(xiàn)細節(jié)。
服務限流
顧名思義,限流的意思就是對訪問服務的流量進行限制,避免高流量、尤其是突發(fā)性的高流量壓垮服務。我們希望服務能通過限流,基于自身可控的速度來消費外部請求,如下圖所示:
為了實現(xiàn)這個目標,業(yè)界也有一些常見的、且被實踐證明可用的方法,包括用于流量控制的計數(shù)器法和滑動窗口法,以及用于流量整形的漏桶算法和令牌桶算法。
計數(shù)器法
基于流量計數(shù)器的限流算法的基本思想非常簡單,就是控制一定時間內(nèi)的請求數(shù)量,如果這個請求量能夠控制在合理的訪問閾值內(nèi),我們就認為能起到限流的效果。具體的實現(xiàn)方法,就是設置一個計數(shù)器,把來自外部的請求量都記錄下來并進行統(tǒng)計。
我給你舉個例子。假設計數(shù)器的閾值為 10,那么當單位時間內(nèi),系統(tǒng)累計的請求量超過了 10,我們就可以直接拒絕掉超額的部分。計數(shù)器的執(zhí)行效果如下所示:
當然,這種做法需要在一個單位時間結束的時候,把計數(shù)器累積的請求量清零。
計數(shù)器算法可以說是限流算法里最簡單也最容易實現(xiàn)的一種算法,但它也有一個不得不面對的問題,即臨界問題,什么意思呢?
我們來想象一個場景:我們設定單位時間是 10 秒鐘,閾值同樣是 10。現(xiàn)在,假設服務請求在第 10 秒快要結束的時候,一次發(fā)送了 6 個請求,這時候一個單位時間結束,計數(shù)器清零。而在第 11 秒開始的瞬間,原則上又可以發(fā)送 8 個請求。這樣在一段很短的時間內(nèi),服務 A 相當于接收到了 6+8=14 個請求,超過了限流的閾值,就可能導致服務失敗。整個過程如下圖所示:
滑動窗口法
那么面對這種情況,我們該怎么辦呢?
答案是可以采用滑動窗口機制來進行限流。滑動窗口可以實現(xiàn)平滑的基于時間片段的統(tǒng)計,其本質上是把一個單位時間進行拆分,從而形成多個時間段。
比方說,我們可以把 10 秒這個單位時間劃分成 10 個時間格,這樣每格代表 1 秒鐘。每過 1 秒鐘,時間窗口就會往前滑動 1 個時間格:
圖片
請注意,這時候每一個時間格都有自己獨立的計數(shù)器,滑動窗口能解決臨界問題的關鍵就在這里。我們假設在第一個時間臨界點,也就是第 10 秒的時候,前后各有 6 個和 8 個請求到達系統(tǒng),那么當?shù)?10 秒結束時,因為當前窗口向前滑動了一個時間格,所以這時候時間窗內(nèi)的請求數(shù)量就是 6+8=14 個,超過了閾值的 10 個,從而就會觸發(fā)限流機制。
從這個角度講,滑動窗口也是一個計數(shù)器算法,只不過它的單位時間會設置得更加細化,從而能夠更好地應對流量的突發(fā)性變化。
漏桶算法
講完用于流量限制的常見做法之后,我們再來看看流量整形的兩種實現(xiàn)方法。
我們先來看下漏桶算法。漏斗我們應該都比較熟悉,它的基本特征就是進口大、出口小,不管我們以怎樣的速度往漏斗里倒入液體,但這些液體從出口流出的速率總是一樣的。
這樣,我們就可以把服務調用量看作是液體,那么不管服務請求的變化多么劇烈,通過漏桶算法進行整形之后,得到的就是具有固定速度的請求量:
圖片
從這張圖中,我們能看到請求 1~9 以不同的速率進入漏桶,而輸出的則是固定速率的請求 1~5,剩余的請求會繼續(xù)留在漏桶。
這里你要注意,漏桶本身的容量肯定也不是無限大的,所以當請求數(shù)量超過了桶的容量,新來的請求也只能被丟棄掉了。
令牌桶算法
講到這里,實際上我們也可以看出漏桶算法的一個缺點,因為漏桶的容量是有限的,而輸出的速率又是固定的,所以當面對突發(fā)的請求流量時,我們無法有效應對流量的變化。這時候,使用令牌桶算法則是更為合適的一種選擇。
令牌桶算法的結構如下圖所示:
圖片
令牌桶算法的具體實現(xiàn)是這樣的:我們以固定的速度將令牌放入桶中,一個令牌對應一個請求。如果在某一時間點上,桶中已經(jīng)沒有令牌了,那么請求就不會得到響應,而如果桶中存放著很多令牌,就可以響應很多的請求,因此服務請求的輸入和輸出都可以是變速的。
由此可見,和漏桶算法不一樣的是,令牌桶算法應對突發(fā)流量的解決方法是允許出現(xiàn)動態(tài)的輸出速率。
以上就是服務限流方案的實現(xiàn)原理,接下來我們來看看服務降級的策略。
服務降級
所謂服務降級,指的是在服務器壓力劇增的情況下,根據(jù)當前業(yè)務情況及流量對一些服務執(zhí)行有策略的快速失敗處理,以避免服務之間的調用依賴影響到其他核心服務:
圖片
具體要如何實現(xiàn)服務降級呢?
業(yè)界的主流做法就是進行服務熔斷。服務熔斷的原理和電路熔斷很像,我們知道在電路系統(tǒng)中一般都會設計一個熔斷器(Circuit Breaker),確保當電流過大時能夠自動切斷電路。而在分布式系統(tǒng)中,熔斷的含義就是當某一個服務已經(jīng)無法正常響應請求時,其他服務就不再繼續(xù)向它發(fā)起請求,以確保調用鏈路不會發(fā)生雪崩效應。
從設計理念上講,當服務消費者向服務提供者發(fā)起遠程調用時,服務熔斷器就會監(jiān)控這次調用,如果調用的響應時間過長,那么熔斷器就會中斷本次調用并直接返回。請注意,熔斷器判斷本次調用是否應該快速失敗是有狀態(tài)的,也就是說熔斷器會把所有的調用結果都記錄下來,只有發(fā)生異常的調用次數(shù)達到一定閾值,才會觸發(fā)熔斷機制,而不是一有異常就直接進行熔斷。
圖片
因此,既然熔斷器內(nèi)部是有狀態(tài)的,那我們就可以對這些狀態(tài)進行抽象和提煉,從而得到如下所示的狀態(tài)轉換圖:
圖片
這個狀態(tài)轉換的具體過程是這樣的:
首先,如電路系統(tǒng)一樣,在默認情況下熔斷器都是關閉的,任何請求都會得到響應。系統(tǒng)會記錄所有的請求處理結果,并會統(tǒng)計失敗的調用次數(shù),看是否已經(jīng)達到了規(guī)定的熔斷閾值。
然后,一旦失敗調用次數(shù)到達閾值,熔斷器就會打開。對于任何請求而言,都將得到失敗的響應結果。請注意,這時候系統(tǒng)的請求處理過程是不會真正執(zhí)行遠程調用的,而是會執(zhí)行快速失敗策略。而一旦發(fā)生熔斷,熔斷器內(nèi)部會啟動一個時鐘,一段時間之后就會自動進入半熔斷狀態(tài)。
處于半熔斷狀態(tài)的熔斷器會允許一部分請求真正得到響應,同時也會統(tǒng)計調用成功的次數(shù)。如果成功的次數(shù)到達一定的數(shù)量,我們就認為系統(tǒng)已經(jīng)恢復正常,熔斷器就會關閉。而如果請求還是會不斷發(fā)生錯誤,那么熔斷器又會被重新打開。
好,以上就是這節(jié)課的主要內(nèi)容。在日常開發(fā)過程中,我們可以使用服務限流和降級機制為分布系統(tǒng)提升服務訪問的可靠性。