理論+實踐,教你如何使用Nginx實現限流
摘要:Nginx作為一款高效能的Web代理和負載均衡伺服器,往往會部署在一些網際網路應用比較前置的位置。此時,我們就可以在Nginx上進行設定,對訪問的IP地址和併發數進行相應的限制。
本文分享自華為雲社群《【高併發】使用Nginx實現限流》,作者:冰 河。
Nginx作為一款高效能的Web代理和負載均衡伺服器,往往會部署在一些網際網路應用比較前置的位置。此時,我們就可以在Nginx上進行設定,對訪問的IP地址和併發數進行相應的限制。
Nginx官方的限流模組
Nginx官方版本限制IP的連線和併發分別有兩個模組:
- limit_req_zone 用來限制單位時間內的請求數,即速率限制,採用的漏桶演算法 “leaky bucket”。
- limit_req_conn 用來限制同一時間連線數,即併發限制。
limit_req_zone 引數配置
limit_req_zone引數說明
Syntax: limit_req zone=name [burst=number] [nodelay];
Default: —
Context: http, server, location
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
- 第一個引數:$binary_remote_addr 表示通過remote_addr這個標識來做限制,“binary_”的目的是縮寫記憶體佔用量,是限制同一客戶端ip地址。
- 第二個引數:zone=one:10m表示生成一個大小為10M,名字為one的記憶體區域,用來儲存訪問的頻次資訊。
- 第三個引數:rate=1r/s表示允許相同標識的客戶端的訪問頻次,這裡限制的是每秒1次,還可以有比如30r/m的。
limit_req zone=one burst=5 nodelay;
- 第一個引數:zone=one 設定使用哪個配置區域來做限制,與上面limit_req_zone 裡的name對應。
- 第二個引數:burst=5,重點說明一下這個配置,burst爆發的意思,這個配置的意思是設定一個大小為5的緩衝區當有大量請求(爆發)過來時,超過了訪問頻次限制的請求可以先放到這個緩衝區內。
- 第三個引數:nodelay,如果設定,超過訪問頻次而且緩衝區也滿了的時候就會直接返回503,如果沒有設定,則所有請求會等待排隊。
limit_req_zone示例
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
server {
location /search/ {
limit_req zone=one burst=5 nodelay;
}
}
下面配置可以限制特定UA(比如搜尋引擎)的訪問:
limit_req_zone $anti_spider zone=one:10m rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* "googlebot|bingbot|Feedfetcher-Google") {
set $anti_spider $http_user_agent;
}
其他引數
Syntax: limit_req_log_level info | notice | warn | error;
Default:
limit_req_log_level error;
Context: http, server, location
當伺服器由於limit被限速或快取時,配置寫入日誌。延遲的記錄比拒絕的記錄低一個級別。例子:limit_req_log_level notice延遲的的基本是info。
Syntax: limit_req_status code;
Default:
limit_req_status 503;
Context: http, server, location
設定拒絕請求的返回值。值只能設定 400 到 599 之間。
ngx_http_limit_conn_module 引數配置
ngx_http_limit_conn_module 引數說明
這個模組用來限制單個IP的請求數。並非所有的連線都被計數。只有在伺服器處理了請求並且已經讀取了整個請求頭時,連線才被計數。
Syntax: limit_conn zone number;
Default: —
Context: http, server, location
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location /download/ {
limit_conn addr 1;
}
一次只允許每個IP地址一個連線。
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;
server {
...
limit_conn perip 10;
limit_conn perserver 100;
}
可以配置多個limit_conn指令。例如,以上配置將限制每個客戶端IP連線到伺服器的數量,同時限制連線到虛擬伺服器的總數。
Syntax: limit_conn_zone key zone=name:size;
Default: —
Context: http
limit_conn_zone $binary_remote_addr zone=addr:10m;
在這裡,客戶端IP地址作為關鍵。請注意,不是$ remote_addr,而是使用$ binary_remote_addr變數。 $ remote_addr變數的大小可以從7到15個位元組不等。儲存的狀態在32位平臺上佔用32或64位元組的記憶體,在64位平臺上總是佔用64位元組。對於IPv4地址,$ binary_remote_addr變數的大小始終為4個位元組,對於IPv6地址則為16個位元組。儲存狀態在32位平臺上始終佔用32或64個位元組,在64位平臺上佔用64個位元組。一個兆位元組的區域可以保持大約32000個32位元組的狀態或大約16000個64位元組的狀態。如果區域儲存耗盡,伺服器會將錯誤返回給所有其他請求。
Syntax: limit_conn_log_level info | notice | warn | error;
Default:
limit_conn_log_level error;
Context: http, server, location
當伺服器限制連線數時,設定所需的日誌記錄級別。
Syntax: limit_conn_status code;
Default:
limit_conn_status 503;
Context: http, server, location
設定拒絕請求的返回值。
Nginx限流實戰
限制訪問速率
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server {
location / {
limit_req zone=mylimit;
}
}
上述規則限制了每個IP訪問的速度為2r/s,並將該規則作用於根目錄。如果單個IP在非常短的時間內併發傳送多個請求,結果會怎樣呢?

我們使用單個IP在10ms內發併發送了6個請求,只有1個成功,剩下的5個都被拒絕。我們設定的速度是2r/s,為什麼只有1個成功呢,是不是Nginx限制錯了?當然不是,是因為Nginx的限流統計是基於毫秒的,我們設定的速度是2r/s,轉換一下就是500ms內單個IP只允許通過1個請求,從501ms開始才允許通過第二個請求。
burst快取處理
我們看到,我們短時間內傳送了大量請求,Nginx按照毫秒級精度統計,超出限制的請求直接拒絕。這在實際場景中未免過於苛刻,真實網路環境中請求到來不是勻速的,很可能有請求“突發”的情況,也就是“一股子一股子”的。Nginx考慮到了這種情況,可以通過burst關鍵字開啟對突發請求的快取處理,而不是直接拒絕。
來看我們的配置:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server {
location / {
limit_req zone=mylimit burst=4;
}
}
我們加入了burst=4,意思是每個key(此處是每個IP)最多允許4個突發請求的到來。如果單個IP在10ms內傳送6個請求,結果會怎樣呢?

相比例項一成功數增加了4個,這個我們設定的burst數目是一致的。具體處理流程是:1個請求被立即處理,4個請求被放到burst佇列裡,另外一個請求被拒絕。通過burst引數,我們使得Nginx限流具備了快取處理突發流量的能力。
但是請注意:burst的作用是讓多餘的請求可以先放到佇列裡,慢慢處理。如果不加nodelay引數,佇列裡的請求不會立即處理,而是按照rate設定的速度,以毫秒級精確的速度慢慢處理。
nodelay降低排隊時間
在使用burst快取處理中,我們看到,通過設定burst引數,我們可以允許Nginx快取處理一定程度的突發,多餘的請求可以先放到佇列裡,慢慢處理,這起到了平滑流量的作用。但是如果佇列設定的比較大,請求排隊的時間就會比較長,使用者角度看來就是RT變長了,這對使用者很不友好。有什麼解決辦法呢?nodelay引數允許請求在排隊的時候就立即被處理,也就是說只要請求能夠進入burst佇列,就會立即被後臺worker處理,請注意,這意味著burst設定了nodelay時,系統瞬間的QPS可能會超過rate設定的閾值。nodelay引數要跟burst一起使用才有作用。
延續burst快取處理的配置,我們加入nodelay選項:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server {
location / {
limit_req zone=mylimit burst=4 nodelay;
}
}
單個IP 10ms內併發傳送6個請求,結果如下:

跟burst快取處理相比,請求成功率沒變化,但是總體耗時變短了。這怎麼解釋呢?在burst快取處理中,有4個請求被放到burst隊列當中,工作程序每隔500ms(rate=2r/s)取一個請求進行處理,最後一個請求要排隊2s才會被處理;這裡,請求放入佇列跟burst快取處理是一樣的,但不同的是,佇列中的請求同時具有了被處理的資格,所以這裡的5個請求可以說是同時開始被處理的,花費時間自然變短了。
但是請注意,雖然設定burst和nodelay能夠降低突發請求的處理時間,但是長期來看並不會提高吞吐量的上限,長期吞吐量的上限是由rate決定的,因為nodelay只能保證burst的請求被立即處理,但Nginx會限制佇列元素釋放的速度,就像是限制了令牌桶中令牌產生的速度。
看到這裡你可能會問,加入了nodelay引數之後的限速演算法,到底算是哪一個“桶”,是漏桶演算法還是令牌桶演算法?當然還算是漏桶演算法。考慮一種情況,令牌桶演算法的token為耗盡時會怎麼做呢?由於它有一個請求佇列,所以會把接下來的請求快取下來,快取多少受限於佇列大小。但此時快取這些請求還有意義嗎?如果server已經過載,快取佇列越來越長,RT越來越高,即使過了很久請求被處理了,對使用者來說也沒什麼價值了。所以當token不夠用時,最明智的做法就是直接拒絕使用者的請求,這就成了漏桶演算法。
自定義返回值
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server {
location / {
limit_req zone=mylimit burst=4 nodelay;
limit_req_status 598;
}
}
預設情況下 沒有配置 status 返回值的狀態:


- 使用卷積神經網路實現圖片去摩爾紋
- 核心不中斷前提下,Gaussdb(DWS)記憶體報錯排查方法
- 簡述幾種常用的排序演算法
- 自動調優工具AOE,讓你的模型在昇騰平臺上高效執行
- GaussDB(DWS)運維:導致SQL執行不下推的改寫方案
- 詳解目標檢測模型的評價指標及程式碼實現
- CosineWarmup理論與程式碼實戰
- 淺談DWS函數出參方式
- 程式碼實戰帶你瞭解深度學習中的混合精度訓練
- python進階:帶你學習實時目標跟蹤
- Ascend CL兩種資料預處理的方式:AIPP和DVPP
- 詳解ResNet 網路,如何讓網路變得更“深”了
- 帶你掌握如何檢視並讀懂昇騰平臺的應用日誌
- InstructPix2Pix: 動動嘴皮子,超越PS
- 何為神經網路卷積層?
- 在昇騰平臺上對TensorFlow網路進行效能調優
- 介紹3種ssh遠端連線的方式
- 分散式資料庫架構路線大揭祕
- DBA必備的Mysql知識點:資料型別和運算子
- 5個高併發導致數倉資源類報錯分析