問題背景
生產環(huán)境中的 Nginx 限流需求通常來自以下場景:
接口被惡意刷:攻擊者用腳本高頻調用登錄/注冊/下單接口
爬蟲過度抓取:搜索引擎爬蟲或采集程序在短時間內發(fā)起大量請求,耗盡后端資源
突發(fā)流量沖擊:促銷活動、熱點事件導致流量瞬間暴增,后端來不及擴容
單個用戶濫用:API 調用頻率超出合理范圍,影響其他正常用戶
Nginx 內置的ngx_http_limit_req_module(基于漏桶算法)和ngx_http_limit_conn_module(基于并發(fā)連接數)可以滿足絕大部分限流需求,無需引入額外的限流組件。
本文從基礎配置到生產級最佳實踐,涵蓋 IP 限流、API Key 限流、白名單繞過、自定義錯誤響應、灰度驗證等場景。
一、核心原理:漏桶算法
Nginx 的limit_req模塊基于漏桶(Leaky Bucket)算法實現。類比一個底部有小孔的水桶:
rate:水桶底部小孔的流水速率 —— 即允許通過的請求速率
burst:水桶的容量 —— 突發(fā)請求的緩沖隊列
nodelay/delay:是否讓超出 burst 的請求排隊等待
關鍵區(qū)別(相比令牌桶):
漏桶強制限流:輸出的速率是恒定的,適用于保護后端不被突發(fā)流量沖垮
令牌桶允許突發(fā):短期內可用完積累的令牌,適用于需要應對突發(fā)流量的場景
Nginx 的limit_req屬于漏桶實現,但通過burst + nodelay參數可以模擬令牌桶的行為。
二、配置指令詳解
2.1 核心指令
| 指令 | 作用域 | 說明 |
|---|---|---|
| limit_req_zone | http | 定義共享內存區(qū)域和請求速率 |
| limit_req | http/server/location | 在指定范圍內啟用限流 |
| limit_req_status | http/server/location | 自定義拒絕響應碼(默認 503,推薦 429) |
| limit_req_log_level | http/server/location | 拒絕和延遲的日志級別 |
| limit_req_dry_run | http/server/location | 1.17.1+ 干跑模式(只統(tǒng)計不限制) |
2.2 limit_req_zone 參數
http {
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
# ^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^
# key(限流依據) zone名:內存大小 速率
}
$binary_remote_addr:二進制格式的客戶端 IP。IPv4 占 4 字節(jié),IPv6 占 16 字節(jié),比$remote_addr(字符串形式的 IP)更節(jié)省內存。推薦使用$binary_remote_addr。
zone=name:size:共享內存名稱和大小。64 位系統(tǒng)上每個狀態(tài)約 128 字節(jié),10MB ≈ 可存儲 8 萬個客戶端狀態(tài)。
rate=10r/s:每秒最多 10 個請求。支持r/s(每秒)和r/m(每分鐘),如30r/m表示每分鐘 30 個請求。
2.3 limit_req 參數
location /api/ {
limit_req zone=per_ip burst=20 nodelay;
# ^^^^^^^ ^^^^^^^^^
# 指定zone 突發(fā)隊列大小
}
zone:指定使用的限流區(qū)域名稱
burst:允許的突發(fā)請求數。當請求超過rate后,超出的請求會進入一個隊列等待。burst=20表示隊列容量為 20。
nodelay:如果加上 nodelay,隊列中的請求不延遲處理,直接放行。如果不加 nodelay,超出 rate 的請求會被延遲處理(即排隊等待),導致響應變慢。
delay=N:1.15.7+ 引入的精細控制。前 N 個超出請求不延遲,后續(xù)超出請求才延遲。
三、基礎配置示例
3.1 基于 IP 限流(最常用)
http {
# 定義限流區(qū)域:依據客戶端 IP,每秒最多 10 次請求
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 80;
server_name api.example.com;
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
這個配置的效果:
每個 IP 每秒最多 10 次請求
允許突發(fā) 20 個請求(超過 10r/s 后的 20 個請求會被立即處理)
超過 10r/s + 20 burst 的請求返回 429
不使用 nodelay 的話,超出 rate 的請求會被延遲到下一周期處理,每個請求的響應時間會逐漸增加
理解 burst + nodelay 的行為:
假設rate=10r/s, burst=20, nodelay:
第 1 秒內收到 30 個請求:前 10 個正常處理,中間 20 個通過 burst 立即處理,超過 30 個返回 429
第 2 秒內只收到 5 個請求:正常處理,同時 burst 隊列"恢復"容量
如果不加 nodelay:
第 1 秒內收到 30 個請求:前 10 個正常處理,中間 20 個排隊以 10r/s 的速率處理,所以需要 2 秒才能全部處理完
最后 10 個請求的響應時間會顯著增加
3.2 基于 API Key 限流
適用于多租戶 API 場景:不同 API Key 有不同的速率限制。
http {
# 依據請求頭 X-API-Key 的值限流
limit_req_zone $http_x_api_key zone=per_key:10m rate=100r/m;
server {
location /v1/ {
# 如果請求頭中沒有 API Key,拒絕
if ($http_x_api_key = "") {
return 401;
}
limit_req zone=per_key burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
}
注意:這里用了$http_x_api_key,這是 Nginx 變量命名規(guī)則——HTTP 請求頭X-API-Key對應變量$http_x_api_key(全小寫、下劃線替換橫線)。
3.3 組合限流:IP + Key 雙重保護
同時限制單 IP 和單 API Key 的請求頻率:
http {
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=5r/s;
limit_req_zone $http_x_api_key zone=per_key:10m rate=100r/m;
server {
location /v1/ {
# 同時應用 IP 限流和 Key 限流(與關系)
limit_req zone=per_ip burst=10 nodelay;
limit_req zone=per_key burst=20 nodelay;
proxy_pass http://backend;
}
}
}
注意:多個limit_req指令是與(AND)關系——必須同時滿足所有規(guī)則。記錄日志時會分別記錄每條規(guī)則的命中情況。
四、高級配置場景
4.1 delay 參數精細控制(1.15.7+)
nodelay是"要么直接放行,要么直接拒絕",適合需要嚴格保護的接口。但過于剛性,可能導致正常用戶在突發(fā)后瞬間被限。
delay參數提供了中間狀態(tài):超出 rate 一定量后才開始延遲。
location /api/ {
limit_req zone=per_ip burst=12 delay=8;
# ^^^^^^^^^ ^^^^^^^
# burst=12 delay=8: 前 8 個超出請求不延遲
}
行為說明(rate=10r/s, burst=12, delay=8):
| 時間窗口內請求數 | 行為 |
|---|---|
| ≤ 10 個 | 全部正常處理 |
| 11~18 個 | 前 10 個正常,超出 8 個以內直接處理(不延遲) |
| 19~22 個 | 前 10 個正常,超出 8 個直接處理,再超出部分延遲處理 |
| > 22 個 | 前 10 個正常,超出 8 個直接處理,部分延遲,部分直接 429 |
delay參數的適用場景:允許用戶在短時間內多請求幾個,但也不能完全無限制。
4.2 白名單繞過(geo + map 模塊)
內部 IP 或健康檢查請求不應該被限流。使用geo+map在白名單 IP 后跳過限流:
http {
# 定義白名單
geo $limit {
default 1; # 默認限流
127.0.0.1 0; # 本機不限流
10.0.0.0/8 0; # 內網段不限流
172.16.0.0/12 0; # 內網段
192.168.0.0/16 0; # 內網段
# 可繼續(xù)添加其他白名單 IP 段
}
# 根據 $limit 決定限流 key
map $limit $limit_key {
0 ""; # 空 key 不觸發(fā)限流
1 $binary_remote_addr; # 非白名單使用 IP 作為 key
}
limit_req_zone $limit_key zone=whitelist:10m rate=10r/s;
server {
location /api/ {
limit_req zone=whitelist burst=20 nodelay;
proxy_pass http://backend;
}
}
}
原理:當$limit_key為空字符串時,limit_req_zone不會為這個請求創(chuàng)建狀態(tài),即不觸發(fā)限流。
4.3 自定義錯誤響應
默認限流返回 503 Service Unavailable,但 503 可能被客戶端或瀏覽器重試。推薦統(tǒng)一返回 429(Too Many Requests),并給出 JSON 格式的響應體。
方法一:使用 error_page 攔截
http {
limit_req_status 429;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
error_page 429 = @rate_limit;
location @rate_limit {
internal; # 內部重定向,外部不可訪問
default_type application/json;
add_header Retry-After 5; # 告訴客戶端 5 秒后重試
return 429 '{"code":429,"message":"請求過于頻繁,請稍后重試","retry_after":5}';
}
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}
}
注意:被限流的請求會產生一個內部重定向到 @rate_limit 塊,這比直接return多一層處理開銷。如果對性能要求極高,可以考慮 Lua 限流方案。
方法二:使用變量模板(性能更好,避免內部重定向)
http {
limit_req_status 429;
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
error_page 429 /429.json; # 直接指向一個靜態(tài)文件
proxy_pass http://backend;
}
location = /429.json {
internal;
default_type application/json;
return 429 '{"code":429,"message":"請求過于頻繁"}';
}
}
}
4.4 基于 URI 的差異化限流
不同 API 路徑可能需要不同的限流策略:
http {
# 登錄接口:嚴格控制
limit_req_zone $binary_remote_addr zone=login:10m rate=2r/s;
# 通用 API:中等控制
limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
# 靜態(tài)資源:寬松
limit_req_zone $binary_remote_addr zone=static:10m rate=100r/s;
server {
location /api/login/ {
limit_req zone=login burst=5 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location /api/ {
limit_req zone=api burst=30 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
location /static/ {
limit_req zone=static burst=200 nodelay;
limit_req_status 429;
root /var/www/static;
}
}
}
4.5 干跑模式(Dry Run,1.17.1+)
在正式啟用限流之前,先用干跑模式觀察影響:
http {
limit_req_zone $binary_remote_addr zone=dry:10m rate=10r/s;
server {
location /api/ {
limit_req_dry_run on; # 開啟干跑模式
limit_req zone=dry burst=20 nodelay;
# 干跑模式下不會真正拒絕請求
proxy_pass http://backend;
}
}
}
干跑模式下,Nginx 會在日志中記錄哪些請求"本應被限流",但實際不會拒絕。通過分析$limit_req_status變量(1.17.6+)可以評估限流策略的準確性。
log_format dry_run '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'limit_req_status=$limit_req_status';
# limit_req_status 可能的值:
# PASSED — 放行(未觸發(fā)限流)
# DELAYED — 被延遲(超出 rate)
# REJECTED — 被拒絕(超出 burst)
# DELAYED_DRY_RUN — 干跑模式下被延遲
# REJECTED_DRY_RUN — 干跑模式下被拒絕
五、并發(fā)連接數限制(limit_conn)
limit_req控制的是請求速率(RPS),而limit_conn控制的是并發(fā)連接數。兩者可以配合使用:
http {
# 定義連接數限制區(qū)域
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
server {
location /api/ {
# 限制每個 IP 最多 10 個并發(fā)連接
limit_conn conn_per_ip 10;
limit_conn_status 429;
# 同時限制請求速率
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}
}
limit_conn的典型場景:限制單個用戶的下載連接數(防止多線程下載器耗盡帶寬)、限制 WebSocket 連接數。
六、配置驗證與效果測試
6.1 驗證配置語法
$ nginx -t nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conftestis successful
6.2 限流效果壓測
使用ab(Apache Bench)模擬高并發(fā):
# 發(fā)送 150 個請求,20 并發(fā) $ ab -n 150 -c 20 http://api.example.com/api/test # 查看結果中的請求分布 ... Complete requests: 150 Failed requests: 30 # 被限流的請求 Non-2xx responses: 30 # 返回 429 的請求 ... Requests per second: 45.50 [#/sec] (mean)
更精確的測試(使用wrk):
$ wrk -t4 -c20 -d30s http://api.example.com/api/test
6.3 查看限流日志
# 查看被限流的請求日志
$ tail -f /var/log/nginx/error.log | grep"limiting requests"
2025/08/15 1522 [warn] 1234#0: *57123 limiting requests, excess: 5.0 by zone "per_ip", client: 1.2.3.4, ...
# 查看返回 429 的請求
$ grep"429"/var/log/nginx/access.log | awk'{print $1,$4,$7,$9}'| tail -10
限流日志中的excess值表示超出速率的部分,單位是請求數。配合 access.log 的客戶端 IP,可以定位到哪些 IP 在頻繁請求。
6.4 監(jiān)控指標
如果使用 Nginx Plus 或 ngx_http_stub_status_module,可以采集限流相關指標。在開源版 Nginx 中,主要通過訪問日志和錯誤日志中的429數量來監(jiān)控限流效果。
# 統(tǒng)計每分鐘的 429 數量
$ tail -10000 /var/log/nginx/access.log | awk'{print $4}'| cut -d: -f2 | sort | uniq -c | sort -rn
七、內存估算
創(chuàng)建zone時需要估算內存大小。64 位系統(tǒng)上每個客戶端狀態(tài)占用約 128 字節(jié):
1MB ≈ 8,000 個客戶端 10MB ≈ 80,000 個客戶端 100MB ≈ 800,000 個客戶端
根據業(yè)務的實際 IP 數量估算:
普通業(yè)務系統(tǒng)(日活 10 萬):10MB 足夠
面向公眾的高流量服務:建議 50~100MB
需要區(qū)分 API Key 的場景:zone 大小取決于 API Key 的數量,每個 Key 也是一個獨立狀態(tài)
如果 zone 過小,會出現內存淘汰,導致部分客戶端的狀態(tài)被覆蓋——限流將不再準確。
八、局限性與替代方案
8.1 Nginx 原生限流的局限
| 局限 | 說明 | 替代方案 |
|---|---|---|
| 限流狀態(tài)無法跨節(jié)點共享 | 每個 Nginx 節(jié)點獨立統(tǒng)計,集群下總限流不準 | Redis + Lua、集中式限流網關 |
| 重啟后統(tǒng)計清零 | zone 存儲在共享內存中,重啟 Nginx 后所有計數歸零 | 無持久化方案 |
| 無法按時間段限流 | 不支持"每小時 N 次"的滑動窗口 | 自定義 Lua 腳本 |
| 限流粒度有限 | 原生只支持 IP、請求頭、URL 等變量作為 key | OpenResty + Lua |
8.2 Redis + Lua 補充方案
如果需要集群級別的分布式限流,可以在 Nginx 中嵌入 Lua 腳本(OpenResty / lua-nginx-module):
-- 簡單的分布式滑動窗口限流
localkey ="rate_limit:".. ngx.var.binary_remote_addr
locallimit =10 -- 每秒最大請求數
localwindow =1 -- 窗口大?。耄?
localred = redis:new()
red:set_timeout(100)
localok, err = red:connect("127.0.0.1",6379)
localcurrent = red:incr(key)
ifcurrent ==1then
red:expire(key, window)
end
ifcurrent > limitthen
ngx.exit(429)
end
8.3 連接數限制模塊(limit_conn)的補充
如果限流需求不僅是請求速率,還包括下載帶寬限制,需要ngx_http_limit_conn_module+ 第三方的ngx_http_limit_rate模塊,或者使用 OpenResty。
九、生產環(huán)境部署注意事項
限流之前先上線監(jiān)控:在啟用限流前,先確認 Nginx 的 429 狀態(tài)碼已在監(jiān)控中,否則限流上線后無法評估效果。
先干跑再正式:新上線的限流策略先用limit_req_dry_run觀察至少一個業(yè)務周期,確認不會誤傷正常用戶后再正式啟用。
限流閾值留有余量:不要卡著業(yè)務 QPS 的極限設置限流閾值。比如業(yè)務單機 QPS 峰值是 1000,限流設置在 1200 并開啟 burst,而不是精確地設置 1000。
業(yè)務方確認閾值:限流閾值由后端業(yè)務方提供,而不是運維隨意設定。需要確認正常用戶在一次頁面操作中會發(fā)起多少次 API 請求。
限流不等于拒絕:對于重要接口(支付、下單),限流后應該返回友好的提示信息(如"系統(tǒng)繁忙,請稍后重試"),而不是直接返回一個難看的 429 頁面。
限流需要配合降級:限流是保護手段,不是解決方案。如果限流失效(如攻擊流量超過 Nginx 處理能力),需要配合 WAF、CDN、DDoS 高防等其他手段組合防御。
十、總結
Nginx 限流配置的核心三要素:
1. limit_req_zone(定義限流規(guī)則) → 2. limit_req(應用到指定位置) → 3. limit_req_status(自定義拒絕碼)
| 場景 | 推薦配置 |
|---|---|
| 登錄接口防刷 | rate=2r/s burst=5 nodelay |
| 普通 API | rate=20r/s burst=30 nodelay |
| 多租戶 API | Key 作為 key + 單租戶速率配置 |
| 內部系統(tǒng) | IP 白名單跳過限流 |
| 大促活動 | 臨時降低 rate,配合 error_page 返回排隊頁 |
生產環(huán)境的最佳實踐:
用429而不是503表示限流拒絕,方便監(jiān)控和排查
用delay=N參數讓突發(fā)請求中的小部分被延遲而非直接拒絕
用$binary_remote_addr而不是$remote_addr作為 key
用 geo + map 模塊在白名單 IP 上跳過限流
新策略先干跑模式驗證,再正式啟用
-
算法
+關注
關注
23文章
4813瀏覽量
98712 -
nginx
+關注
關注
0文章
199瀏覽量
13237
原文標題:Nginx 限流怎么做?防刷、防爬、防接口打爆實戰(zhàn)
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
【NanoPi NEO試用體驗】之安裝配置Nginx環(huán)境WEB網站詳解
主要學習下nginx的安裝配置
APISIX Ingress VS Ingress NGINX詳細對比
限流方案常用算法 常用的限流方案
nginx中的正則表達式和location路徑匹配指南
云原生環(huán)境里Nginx的故障排查思路
生產環(huán)境中的Nginx限流策略
評論