微服務(wù)中的限流邏輯與算法
本文轉(zhuǎn)載自微信公眾號「無敵碼農(nóng)」,作者無敵碼農(nóng)。轉(zhuǎn)載本文請聯(lián)系無敵碼農(nóng)公眾號。
在微服務(wù)架構(gòu)中一個會被經(jīng)常提及的概念就是“服務(wù)的熔斷與限流”。而之所以如此頻繁的提及這個概念,是因為在高并發(fā)場景下,瞬間的流量洪峰很容易超出微服務(wù)中各個系統(tǒng)的最大承受能力,從而造成服務(wù)的整體不可用。
所以在設(shè)計高并發(fā)場景下的微服務(wù)架構(gòu)時,要根據(jù)服務(wù)所能承受的最大流量制定限流策略,從而保證在高并發(fā)場景下服務(wù)的穩(wěn)定性。今天的文章就和大家聊一聊關(guān)于限流的內(nèi)容,包括常見的限流算法及目前微服務(wù)架構(gòu)中主要的限流框架。
限流的概念
先介紹下什么是限流?其實日常生活中的限流場景隨處可見,例如北京地鐵早高峰,每天都是人山人海,如果大家一起蜂擁進(jìn)站,將很容易造成站內(nèi)擁堵,所以地鐵站一般都會進(jìn)站口設(shè)置像迷宮一樣的柵欄,大家轉(zhuǎn)圈圈分批進(jìn)站,這就是一種典型的限流場景。
那么在微服務(wù)中的限流具體是指什么呢?從字面上看,限流限的是流量,在不同場景下流量的定義是不同的,可以是QPS(每秒請求數(shù))、TPS(每秒事務(wù)處理數(shù)),也可以指純粹的網(wǎng)絡(luò)流量(如網(wǎng)卡通過的字節(jié)數(shù))等。
但我們通常所說的限流,是指限制達(dá)到系統(tǒng)的并發(fā)請求數(shù),使得系統(tǒng)能夠在自身能力允許的情況下正常處理部分用戶的請求,而對超出自身處理能力的用戶請求則予以拒絕,從而保證系統(tǒng)的穩(wěn)定運行。
限流不可避免的會造成用戶請求失敗或變慢的情況,從而在一定程度上影響用戶體驗,所以限流策略的制定需要以系統(tǒng)壓測的結(jié)果為參考,并在用戶體驗與系統(tǒng)穩(wěn)定性之間進(jìn)行平衡取舍。
限流的必要性?
前面我們提到限流的主要目的是為了保證系統(tǒng)的穩(wěn)定性。在日常的業(yè)務(wù)中,如果遇到像雙十一之類的促銷活動,或者遇到爬蟲等不正常的流量等情況,用戶流量突增,但后端服務(wù)的處理能力是有限的,如果不能有效處理突發(fā)流量,那么后端服務(wù)就很容易被打垮。
可以設(shè)想這樣一個場景:"某服務(wù)單節(jié)點可以承受的QPS是1000,該服務(wù)共有5個節(jié)點,日常情況下服務(wù)的QPS為3000"。那么正常情況下該服務(wù)毫無壓力,根據(jù)負(fù)載均衡配置3000/5=600,每個節(jié)點的日常QPS才600左右。
直到某一天,老板突然搞了一波促銷,系統(tǒng)的整體QPS達(dá)到了8000。此時每個節(jié)點的平均承載QPS為1600,節(jié)點A率先扛不住直接掛了,此時集群中還剩下4個節(jié)點,每個節(jié)點的平均承載QPS將達(dá)到2000,于是,剩下的4個節(jié)點也一臺接一臺掛了,整個服務(wù)就此雪崩。
而如果我們的系統(tǒng)有限流機(jī)制,那么情況將會如何發(fā)展呢?
系統(tǒng)整體QPS達(dá)到8000,但由于集群整體限流了5000,所以超出集群承受力的那3000個請求將被拒絕,系統(tǒng)則會正常處理5000個用戶請求,這是對集群整體限流的情況。而對于各個節(jié)點來說,由于我的承受力只是1000QPS,那么超出1000的部分也將被拒絕。這樣雖然損失了部分用戶請求,但保證了整個系統(tǒng)的穩(wěn)定性,也給開發(fā)運維留下了系統(tǒng)擴(kuò)容時間。
由此可見,限流對于系統(tǒng)的自我保護(hù)是非常重要的存在。那么限流具體怎么做呢?接下來我們總結(jié)下常見的限流算法。
常見的限流算法
常見的限流算法主要有:計數(shù)器、固定窗口,滑動窗口、漏桶、令牌桶。接下來我們分別介紹下這幾種限流算法。
<計數(shù)器限流>
計數(shù)器限流是最簡單粗暴的一種限流算法,例如系統(tǒng)能同時處理100個請求,那么可以在保存一個計數(shù)器,處理一個請求,計數(shù)器加一,一個請求處理完畢后計數(shù)器減一。每次請求進(jìn)來的時候,先看一眼計數(shù)器的值,如果超過閥值則直接拒絕。
在具體實現(xiàn)時,如果該計數(shù)器是存在單機(jī)內(nèi)存中,那么就實現(xiàn)了單機(jī)限流;而如果存在例如Redis中,集群中的所有節(jié)點依次為限流依據(jù),那么就算實現(xiàn)了集群限流算法。
優(yōu)點:實現(xiàn)簡單,單機(jī)例如諸如Java的Atomic等原子類就能實現(xiàn),集群則通過Redis的incr操作就能快速實現(xiàn)。
缺點:計數(shù)器限流無法應(yīng)對突發(fā)的流量增長。例如我們允許的閥值是1W,此時計數(shù)器的值是0,那么當(dāng)1W個請求瞬間全部打進(jìn)來的時候,很可能服務(wù)就頂不住了。這是因為流量的緩緩增加和一下子涌入,對系統(tǒng)所產(chǎn)生的壓力是不一樣的。
況且一般限流都是限制在指定時間間隔內(nèi)的訪問量,而不是全時段服務(wù)的總體處理能力,所以計數(shù)器限流不太適合高并發(fā)場景下的限流實現(xiàn)。
<固定窗口限流>
相對于計數(shù)器來說,固定窗口限流是以一段時間窗口內(nèi)的訪問量作為限流的依據(jù),計數(shù)器每過一個時間窗口就自動重置。其規(guī)則如下:
- 請求次數(shù)小于閥值,允許訪問,計數(shù)器加1;
- 請求次數(shù)大于閥值,拒絕訪問;
- 本時間窗口過了之后,計數(shù)器自動清零;
固定窗口限流雖然看起來挺完美,但是它有固定窗口臨界的問題。例如系統(tǒng)每秒允許1000個請求,假如第一個時間窗口的間隔是0~1秒,但在第0.55秒處一下子涌入了1000個請求,過了1秒后計數(shù)清零,此時在1.05秒的時候又一下子涌入了1000個請求。
此時雖然在固定時間窗口內(nèi)的計數(shù)沒有超過閥值,但在全局看來0.55秒~1.05秒這0.5秒內(nèi)一下子卻涌入了2000個請求,而這對于閥值為1000/s的系統(tǒng)來說是不可承受的。如下圖所示:
而為了解決這個問題,衍生出了滑動窗口限流的算法!
<滑動窗口限流>
滑動窗口限流解決了固定窗口臨界值的問題,可以保證任意時間窗口內(nèi)都不會超過限流閥值。相對于固定窗口,滑動窗口除了需要引入計數(shù)器外,還需要額外記錄時間窗口內(nèi)每個請求到達(dá)的時間點。
以時間窗口為1秒為例,規(guī)則如下:
- 記錄每次請求的時間;
- 統(tǒng)計每次請求的時間向前推1秒這個時間窗口內(nèi)的請求數(shù),且1秒前的數(shù)據(jù)可以刪除;
- 統(tǒng)計的請求數(shù)小于閥值則記錄該請求的時間,并允許通過,反之則拒絕該請求;
雖然看起來很OK,但是滑動窗口也無法解決短時間之內(nèi)集中流量的沖擊。例如每秒限制1000個請求,但是有可能存在前5毫秒的時候,閥值就被打滿的情況,理想情況下每10毫秒來100個請求,那么系統(tǒng)對流量的處理就會更加平滑。
但在真實場景中是很難控制請求的頻率的。所以為了解決時間窗口類算法的痛點,又出現(xiàn)了漏桶算法。
<漏桶限流>
漏桶算法的基本思想是,流量持續(xù)進(jìn)入漏桶中,底部則定速處理請求,如果流量進(jìn)入的速率高于底部請求被處理的速率,且當(dāng)桶中的流量超過桶的大小時,流量就會被溢出。具體如下圖所示:
漏桶算法的特點是寬進(jìn)嚴(yán)出,無論請求的速率有多大,底部的處理速度都勻速進(jìn)行。這種算法的特點有點類似于消息隊列的處理機(jī)制,一般來說漏桶算法也是由隊列來實現(xiàn)的。
但漏桶算法的這種特點,實際上即是它的優(yōu)點也是缺點。有時候面對突發(fā)流量,我們往往會希望在保持系統(tǒng)穩(wěn)定的同時,能更快地處理用戶請求以提升用戶體驗,而不是按部就班的佛系工作。在這種情況下又出現(xiàn)了令牌桶這樣的限流算法,它在應(yīng)對突發(fā)流量時,可以比漏桶算法更加激進(jìn)。
<令牌桶限流>
令牌桶與漏桶的原理類似,只是漏桶是底部勻速處理,而令牌桶則是定速的向桶里塞入令牌,然后請求只有拿到了令牌才會被服務(wù)器處理。具體規(guī)則如下:
- 定速的向桶中放入令牌;
- 令牌數(shù)量超過桶的限制,則丟棄;
- 請求來了先向桶中索取令牌,索取成功則通過被處理,否則拒絕;
可以看出令牌桶在應(yīng)對突發(fā)流量時,不會想漏桶那樣勻速的處理,而是在短時間內(nèi)請求可以同時取走桶中的令牌,并及時的被服務(wù)器處理。所以在應(yīng)對突發(fā)流量的場景下,令牌桶表現(xiàn)更強(qiáng)。
限流算法總結(jié)
經(jīng)過上述的描述,好像漏桶、令牌桶比時間窗口類算法好多了,那么時間窗口類算法是不是就沒啥用了呢?
其實并不是,雖然漏桶、令牌桶對比時間窗口類算法對流量的整形效果更好,但是它們也有各自的缺點,例如令牌桶,假如系統(tǒng)上線時沒有預(yù)熱,那么可能會出現(xiàn)由于此時桶中還沒有令牌,而導(dǎo)致請求被誤殺的情況;而漏桶中由于請求是暫存在桶中的,所以請求什么時候能被處理,則是有延時的,這并不符合互聯(lián)網(wǎng)業(yè)務(wù)低延時的要求。
所以令牌桶、漏桶算法更適合阻塞式限流的場景,即后臺任務(wù)類的限流。而基于時間窗口的限流則更適合互聯(lián)網(wǎng)實施業(yè)務(wù)限流的場景,即能處理快速處理,不能處理及時響應(yīng)調(diào)用方,避免請求出現(xiàn)過長的等待時間。
微服務(wù)限流組件
如果你有興趣實際上也是可以自己實現(xiàn)一個限流組件的,只不過這種輪子已經(jīng)早有人造好了。目前市面上比較流行的限流組件主要有:Google Guava提供的限流工具類“RateLimiter”、阿里開源的Sentinel。
其中Google Guava提供的限流工具類“RateLimiter”,是基于令牌桶實現(xiàn)的,并且擴(kuò)展了算法,支持了預(yù)熱功能。而阿里的Sentinel中的勻速限流策略,就是采用了漏桶算法。
原文鏈接:https://mp.weixin.qq.com/s/A_iCvZKr1Gq2hUfV8PJ-RA