Nginx限流特技

小米運維發表於2018-11-19

本文介紹了Nginx限流的技巧及三個實驗對比。

上篇文章回顧:Memcache使用Mcrouter實現高可用

限流(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不夠用時,最明智的做法就是直接拒絕使用者的請求,即漏桶演算法。

Nginx限流特技

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559359/viewspace-2220618/,如需轉載,請註明出處,否則將追究法律責任。

相關文章