Nginx 限流方法

限流(rate limiting)是Nginx眾多特性中最有用的,也是經常容易被誤解和錯誤配置的,特性之一。該特性可以限制某個用戶在一個給定時間段內能夠產生的HTTP請求數。請求可以簡單到就是一個對於主頁的GET請求或者一個登陸表格的POST請求。

限流可以用於安全目的上,通過限制請求速度來防止外部暴力掃描,或者減慢暴力密碼破解攻擊,可以結合日誌標記出目標URL來幫助防範DDoS攻擊,也可以解決流量突發的問題(如整點活動),一般地說,限流是用在保護上游應用服務器不被在同一時刻的大量用戶請求湮沒。

我們在訪問內部域名時在1s時間內發起兩個請求,操作上的一些時差會出現兩種不同的結果,2個請求都返回200,或者1個請求返回200,而另外一個請求返回503,為此對Nginx限流模塊進行了深入調研,Nginx在處理請求的時候是在1s內對請求的具體個數做拆分的,以2request/s為例,拆分為500ms內處理一個請求,下一個500ms才會處理第二個請求;除此之外,我們發現,在限制2request/s後,發現發送多個請求(requests>=2)也可以被處理掉,在此基礎上上,調研了緩衝隊列的相關配置。

1

漏桶和令牌桶算法的概念

漏桶算法(Leaky Bucket):主要目的是控制數據注入到網絡的速率,平滑網絡上的突發流量。漏桶算法提供了一種機制,通過它,突發流量可以被整形以便為網絡提供一個穩定的流量。漏桶算法的示意圖如下圖所示,請求先進入到漏桶裡,漏桶以一定的速度出水,當水請求過大會直接溢出,可以看出漏桶算法能強行限制數據的傳輸速率。


Nginx 限流方法


令牌桶算法(Token Bucket):是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。典型情況下,令牌桶算法用來控制發送到網絡上的數據的數目,並允許突發數據的發送。令牌桶算法示意圖如下圖所示,大小固定的令牌桶可自行以恆定的速率源源不斷地產生令牌。如果令牌不被消耗,或者被消耗的速度小於產生的速度,令牌就會不斷地增多,直到把桶填滿。後面再產生的令牌就會從桶中溢出。最後桶中可以保存的最大令牌數永遠不會超過桶的大小。


Nginx 限流方法


2

兩種算法的區別

兩者主要區別在於“漏桶算法”能夠強行限制數據的傳輸速率,而“令牌桶算法”在能夠限制數據的平均傳輸速率外,還允許某種程度的突發傳輸。在“令牌桶算法”中,只要令牌桶中存在令牌,那麼就允許突發地傳輸數據直到達到用戶配置的門限,所以它適合於具有突發特性的流量。

3

按請求速率限速

按請求速率限速是指限制IP發送請求的速率,超出指定速率後,Nginx將直接拒絕更多的請求。採用漏桶算法實現。下面從一些實驗數據上來深入的瞭解這個模塊,先簡單介紹一下該模塊的配置方式,如下圖所示(配置需要在Nginx配置和域名配置裡面同時修改),使用limit_req_zone關鍵字,定義一個名為tip大小為10MB的共享內存區域(zone),用來存放限速相關的統計信息,限速的key值為二進制的IP地址($binary_remote_addr),限速上限(rate)為2r/s。

將上述規則應用到/search目錄(單個IP的訪問速度被限制在了2請求/秒,超過這個限制的訪問將直接被Nginx拒絕)。burst和nodelay的作用稍後解釋。(zone=tip:10m表示會話空間的存儲大小為10m)。


Nginx 限流方法


4

3個實驗案例

實驗1、討論2個請求在1s內的執行過程

修改配置下圖所示:


Nginx 限流方法


我們使用ab工具模擬1s發送2個請求。


Nginx 限流方法


只有一個請求成功了,查看了一下執行時間:


Nginx 限流方法


1ms內完成了所有請求,考慮到每秒兩個請求可能是分時間段來來完成的,二分法做了大量延遲處理的嘗試,當兩個請求之間的時延大於0.5s時第二個請求才會成功。


Nginx 限流方法


結論:Nginx的限流統計是基於毫秒的,我們設置的速度是2r/s,轉換一下就是500ms內單個IP只允許通過1個請求,從501ms開始才允許通過第二個請求。


Nginx 限流方法


實驗2、burst允許緩存處理突發請求

如果短時間內發送了大量請求,Nginx按照毫秒級精度統計,超出限制的請求直接拒絕。這在實際場景中未免過於苛刻,真實網絡環境中請求到來不是勻速的,很可能有請求“突發”的情況。Nginx考慮到了這種情況,可以通過burst關鍵字開啟對突發請求的緩存處理,而不是直接拒絕。(類似令牌桶算法)

修改Nginx配置如下:


Nginx 限流方法



我們加入了burst=4,意思是每個key(此處是每個IP)最多允許4個突發請求的到來。使用ab工具發送6個請求,結果會怎樣呢?


Nginx 限流方法


發送時間:


Nginx 限流方法


執行結果是請求全部被處理,加入緩衝隊列後,按照之前時間軸的規則 ,在0.001s、0.501s、1.001s、1.501s、2.001s、2.501s分別發送一個請求,與之前的有些偏差,理論上數據應該是同時發送到ngnix,我們做了抓包統進行驗證,可以觀察到6個請求分別開闢了6個端口進行發送,只有當第一個包三次握手完成,第二個包才開始發送,時間間隔500ms,這就驗證了ab工具確實是單個發送的。


Nginx 限流方法


另外,理論上緩衝區之外的2個請求應該只有一個是200,另外一個是503,緩衝區的4個請求是按照2r/s的速度依次處理,這裡是由於ab工具本身的問題,在加入緩衝隊列後,發送時間由之前的1ms內完成變成了2501ms完成,所以導致了請求都被處理掉,若是使用其他工具短時間內發送6個請求,則只能成功5個。


Nginx 限流方法


發送耗時2ms,完成處理時間2437ms,每個請求的處理時間。


Nginx 限流方法


由於ngnix 500ms處理完第一個請求後,501ms才會處理第二個請求,所以5個請求(去掉503那個)耗時500ms*4+416ms=2416ms(本地實測,不同Nginx性能有所差異),或者使用ab工具併發來處理這些請求,也會有同樣的效果。


Nginx 限流方法


我們再來觀察一下發送時間,所有的請求基本在10ms內發起,這樣便導致了6個請求中,去掉第一個和緩衝區的4個,第二個被拒絕掉。


Nginx 限流方法


實驗3、nodelay降低排隊時間

通過設置burst參數,我們可以允許Nginx緩存處理一定程度的突發,多餘的請求可以先放到隊列裡,慢慢處理,這起到了平滑流量的作用。但是如果隊列設置的比較大,請求排隊的時間就會比較長,這對用戶很不友好。nodelay參數允許請求在排隊的時候就立即被處理,也就是說只要請求能夠進入burst隊列,就會立即被後臺worker處理。

延續實驗2的配置,我們加入nodelay選項:


Nginx 限流方法


同樣發送6個請求發送時間:


Nginx 限流方法



實驗結果如下圖所示:


Nginx 限流方法


處理時間:


Nginx 限流方法



與實驗2相比直觀上的效果就是Nginx同時出現6條日誌,即6個請求是同時被處理的,而實驗2日誌是逐條生成的,間隔0.5s,視覺上有卡頓。

雖然設置burst和nodelay能夠降低突發請求的處理時間,但是長期來看並不會提高吞吐量的上限,長期吞吐量的上限是由rate決定的,因為nodelay只能保證burst的請求被立即處理,加入了nodelay參數之後的限速算法還算是漏桶算法,當令牌桶算法的token為耗盡時,由於它有一個請求隊列,所以會把接下來的請求緩存下來,緩存多少受限於隊列大小。假如server已經過載,緩存隊列越來越長,即使過了很久請求被處理了,對用戶來說也沒什麼價值了。所以當token不夠用時,最明智的做法就是直接拒絕用戶的請求,即漏桶算法


分享到:


相關文章: