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