流量控制與令牌桶演算法

潘小鶸發表於2015-11-04

一年一度的「雙 11」又要到了,阿里的碼農們進入了一年中最辛苦的時光。各種容量評估、壓測、擴容讓我們忙得不可開交。洛陽親友如相問,就說我搞雙十一。

如何讓系統在洶湧澎湃的流量面前談笑風生?我們的策略是不要讓系統超負荷工作。如果現有的系統扛不住業務目標怎麼辦?加機器!機器不夠怎麼辦?業務降級,系統限流!

正所謂「他強任他強,清風拂山崗;他橫任他橫,明月照大江」,降級和限流是大促保障中必不可少的神兵利器,丟卒保車,以暫停邊緣業務為代價保障核心業務的資源,以系統不被突發流量壓掛為第一要務。

集團的中介軟體有一個不錯的單機限流框架,支援兩種限流模式:控制速率和控制併發。限流這種東西,應該是來源於網路裡面的「流量整型」,通過控制資料包的傳輸速率和時機,來實現一些效能、服務質量方面的東西。令牌桶是一種常見的流控演算法,屬於控制速率型別的。控制併發則相對要常見的多,比如作業系統裡的「訊號量」就是一種控制併發的方式。

在 Wikipedia 上,令牌桶演算法是這麼描述的:

  1. 每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌
  2. 桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄
  3. 當一個 n 位元組的資料包到達時,消耗 n 個令牌,然後傳送該資料包
  4. 如果桶中可用令牌小於 n,則該資料包將被快取或丟棄

令牌桶控制的是一個時間視窗內的通過的資料量,在 API 層面我們常說的 QPS、TPS,正好是一個時間視窗內的請求量或者事務量,只不過時間視窗限定在 1s 罷了。

現實世界的網路工程中使用的令牌桶,比概念圖中的自然是複雜了許多,「令牌桶」的數量也不是一個而是兩個,簡單的演算法描述可用參考中興的期刊[1]或者 RFC。

假如專案使用 Java 語言,我們可以輕鬆地藉助 Guava 的 RateLimiter 來實現基於令牌桶的流控。RateLimiter 令牌桶演算法的單桶實現,也許是因為在 Web 應用層面單桶實現就夠用了,雙筒實現就屬於過度設計。

RateLimiter 對簡單的令牌桶演算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要注意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶演算法。也許是出於簡單起見,RateLimiter 中的時間視窗能且僅能為 1s,如果想搞其他時間單位的限流,只能另外造輪子。

SmoothBursty 積極響應李克強總理的號召,上個月的流量沒用完,可以挪到下個月用。其實就是 SmoothBursty 有一個可以放 N 個時間視窗產生的令牌的桶,系統空閒的時候令牌就一直攢著,最好情況下可以扛 N 倍於限流值的高峰而不影響後續請求。如果不想像三峽大壩一樣能扛千年一遇的洪水,可以把 N 設定為 1,這樣就只屯一個時間視窗的令牌。

RateLimiter 有一個有趣的特性是「前人挖坑後人跳」,也就是說 RateLimiter 允許某次請求拿走超出剩餘令牌數的令牌,但是下一次請求將為此付出代價,一直等到令牌虧空補上,並且桶中有足夠本次請求使用的令牌為止[2]。這裡面就涉及到一個權衡,是讓前一次請求乾等到令牌夠用才走掉呢,還是讓它先走掉後面的請求等一等呢?Guava 的設計者選擇的是後者,先把眼前的活幹了,後面的事後面再說。

當我們要實現一個基於速率的單機流控框架的時候,RateLimiter 是一個完善的核心元件,就彷彿 Linux 核心對 GNU 作業系統那樣重要。但是我們還需要其他的一些東西才能把一個流控框架跑起來,比如一個通用的 API,一個攔截器,一個線上配置流控閾值的後臺等等。

下面隨便寫了一個簡單的流控框架 API,至於攔截器和後臺就懶得寫了,有時間再自己造一套中介軟體的輪子吧~


  1. QoS技術中令牌桶演算法實現方式比較 
  2. How is the RateLimiter designed, and why? 

相關文章