微服務中的限流邏輯與算法

在微服務架構中一個會被常常說起的概念就是「服務的熔斷與限流」。而之因此如此頻繁的說起這個概念,是由於在高併發場景下,瞬間的流量洪峯很容易超出微服務中各個系統的最大承受能力,從而形成服務的總體不可用。算法

因此在設計高併發場景下的微服務架構時,要根據服務所能承受的最大流量制定限流策略,從而保證在高併發場景下服務的穩定性。今天的文章就和你們聊一聊關於限流的內容,包括常見的限流算法及目前微服務架構中主要的限流框架。後端

限流的概念服務器

先介紹下什麼是限流?其實平常生活中的限流場景隨處可見,例如北京地鐵早高峯,天天都是人山人海,若是你們一塊兒蜂擁進站,將很容易形成站內擁堵,因此地鐵站通常都會進站口設置像迷宮同樣的柵欄,你們轉圈圈分批進站,這就是一種典型的限流場景。網絡

那麼在微服務中的限流具體是指什麼呢?從字面上看,限流限的是流量,在不一樣場景下流量的定義是不一樣的,能夠是QPS(每秒請求數)、TPS(每秒事務處理數),也能夠指純粹的網絡流量(如網卡經過的字節數)等。架構

但咱們一般所說的限流,是指限制達到系統的併發請求數,使得系統可以在自身能力容許的狀況下正常處理部分用戶的請求,而對超出自身處理能力的用戶請求則予以拒絕,從而保證系統的穩定運行。併發

限流不可避免的會形成用戶請求失敗或變慢的狀況,從而在必定程度上影響用戶體驗,因此限流策略的制定須要以系統壓測的結果爲參考,並在用戶體驗與系統穩定性之間進行平衡取捨。負載均衡

限流的必要性?框架

前面咱們提到限流的主要目的是爲了保證系統的穩定性。在平常的業務中,若是遇到像雙十一之類的促銷活動,或者遇到爬蟲等不正常的流量等狀況,用戶流量突增,但後端服務的處理能力是有限的,若是不能有效處理突發流量,那麼後端服務就很容易被打垮。運維

能夠設想這樣一個場景:"某服務單節點能夠承受的QPS是1000,該服務共有5個節點,平常狀況下服務的QPS爲3000"。那麼正常狀況下該服務毫無壓力,根據負載均衡配置3000/5=600,每一個節點的平常QPS才600左右。微服務

直到某一天,老闆忽然搞了一波促銷,系統的總體QPS達到了8000。此時每一個節點的平均承載QPS爲1600,節點A率先扛不住直接掛了,此時集羣中還剩下4個節點,每一個節點的平均承載QPS將達到2000,因而,剩下的4個節點也一臺接一臺掛了,整個服務就此雪崩。

而若是咱們的系統有限流機制,那麼狀況將會如何發展呢?

系統總體QPS達到8000,但因爲集羣總體限流了5000,因此超出集羣承受力的那3000個請求將被拒絕,系統則會正常處理5000個用戶請求,這是對集羣總體限流的狀況。而對於各個節點來講,因爲個人承受力只是1000QPS,那麼超出1000的部分也將被拒絕。這樣雖然損失了部分用戶請求,但保證了整個系統的穩定性,也給開發運維留下了系統擴容時間。

因而可知,限流對於系統的自我保護是很是重要的存在。那麼限流具體怎麼作呢?接下來咱們總結下常見的限流算法。

常見的限流算法

常見的限流算法主要有:計數器、固定窗口,滑動窗口、漏桶、令牌桶。接下來咱們分別介紹下這幾種限流算法。

<計數器限流>

計數器限流是最簡單粗暴的一種限流算法,例如系統能同時處理100個請求,那麼能夠在保存一個計數器,處理一個請求,計數器加一,一個請求處理完畢後計數器減一。每次請求進來的時候,先看一眼計數器的值,若是超過閥值則直接拒絕。

在具體實現時,若是該計數器是存在單機內存中,那麼就實現了單機限流;而若是存在例如Redis中,集羣中的全部節點依次爲限流依據,那麼就算實現了集羣限流算法。

優勢:實現簡單,單機例如諸如Java的Atomic等原子類就能實現,集羣則經過Redis的incr操做就能快速實現。

缺點:計數器限流沒法應對突發的流量增加。例如咱們容許的閥值是1W,此時計數器的值是0,那麼當1W個請求瞬間所有打進來的時候,極可能服務就頂不住了。這是由於流量的緩緩增長和一會兒涌入,對系統所產生的壓力是不同的。

何況通常限流都是限制在指定時間間隔內的訪問量,而不是全時段服務的整體處理能力,因此計數器限流不太適合高併發場景下的限流實現。

<固定窗口限流>

相對於計數器來講,固定窗口限流是以一段時間窗口內的訪問量做爲限流的依據,計數器每過一個時間窗口就自動重置。其規則以下:

  • 請求次數小於閥值,容許訪問,計數器加1;
  • 請求次數大於閥值,拒絕訪問;
  • 本時間窗口過了以後,計數器自動清零;

固定窗口限流雖然看起來挺完美,可是它有固定窗口臨界的問題。例如系統每秒容許1000個請求,假如第一個時間窗口的間隔是0~1秒,但在第0.55秒處一會兒涌入了1000個請求,過了1秒後計數清零,此時在1.05秒的時候又一會兒涌入了1000個請求。

此時雖然在固定時間窗口內的計數沒有超過閥值,但在全局看來0.55秒~1.05秒這0.5秒內一會兒卻涌入了2000個請求,而這對於閥值爲1000/s的系統來講是不可承受的。以下圖所示:

而爲了解決這個問題,衍生出了滑動窗口限流的算法!

<滑動窗口限流>

滑動窗口限流解決了固定窗口臨界值的問題,能夠保證任意時間窗口內都不會超過限流閥值。相對於固定窗口,滑動窗口除了須要引入計數器外,還須要額外記錄時間窗口內每一個請求到達的時間點。

以時間窗口爲1秒爲例,規則以下:

  • 記錄每次請求的時間;
  • 統計每次請求的時間向前推1秒這個時間窗口內的請求數,且1秒前的數據能夠刪除;
  • 統計的請求數小於閥值則記錄該請求的時間,並容許經過,反之則拒絕該請求;

雖然看起來很OK,可是滑動窗口也沒法解決短期以內集中流量的衝擊。例如每秒限制1000個請求,可是有可能存在前5毫秒的時候,閥值就被打滿的狀況,理想狀況下每10毫秒來100個請求,那麼系統對流量的處理就會更加平滑。

但在真實場景中是很難控制請求的頻率的。因此爲了解決時間窗口類算法的痛點,又出現了漏桶算法。

<漏桶限流>

漏桶算法的基本思想是,流量持續進入漏桶中,底部則定速處理請求,若是流量進入的速率高於底部請求被處理的速率,且當桶中的流量超過桶的大小時,流量就會被溢出。具體以下圖所示:

漏桶算法的特色是寬進嚴出,不管請求的速率有多大,底部的處理速度都勻速進行。這種算法的特色有點相似於消息隊列的處理機制,通常來講漏桶算法也是由隊列來實現的。

但漏桶算法的這種特色,實際上便是它的優勢也是缺點。有時候面對突發流量,咱們每每會但願在保持系統穩定的同時,能更快地處理用戶請求以提高用戶體驗,而不是循序漸進的佛系工做。在這種狀況下又出現了令牌桶這樣的限流算法,它在應對突發流量時,能夠比漏桶算法更加激進。

<令牌桶限流>

令牌桶與漏桶的原理相似,只是漏桶是底部勻速處理,而令牌桶則是定速的向桶裏塞入令牌,而後請求只有拿到了令牌纔會被服務器處理。具體規則以下:

  • 定速的向桶中放入令牌;
  • 令牌數量超過桶的限制,則丟棄;
  • 請求來了先向桶中索取令牌,索取成功則經過被處理,不然拒絕;

能夠看出令牌桶在應對突發流量時,不會想漏桶那樣勻速的處理,而是在短期內請求能夠同時取走桶中的令牌,並及時的被服務器處理。因此在應對突發流量的場景下,令牌桶表現更強。

限流算法總結

通過上述的描述,好像漏桶、令牌桶比時間窗口類算法好多了,那麼時間窗口類算法是否是就沒啥用了呢?

其實並非,雖然漏桶、令牌桶對比時間窗口類算法對流量的整形效果更好,可是它們也有各自的缺點,例如令牌桶,假如系統上線時沒有預熱,那麼可能會出現因爲此時桶中尚未令牌,而致使請求被誤殺的狀況;而漏桶中因爲請求是暫存在桶中的,因此請求何時能被處理,則是有延時的,這並不符合互聯網業務低延時的要求。

因此令牌桶、漏桶算法更適合阻塞式限流的場景,即後臺任務類的限流。而基於時間窗口的限流則更適合互聯網實施業務限流的場景,即能處理快速處理,不能處理及時響應調用方,避免請求出現過長的等待時間。

微服務限流組件

若是你有興趣實際上也是能夠本身實現一個限流組件的,只不過這種輪子已經早有人造好了。目前市面上比較流行的限流組件主要有:Google Guava提供的限流工具類「RateLimiter」、阿里開源的Sentinel。

其中Google Guava提供的限流工具類「RateLimiter」,是基於令牌桶實現的,而且擴展了算法,支持了預熱功能。而阿里的Sentinel中的勻速限流策略,就是採用了漏桶算法。

相關文章
相關標籤/搜索