问题背景
生产环境中的 Nginx 限流需求通常来自以下场景:
接口被恶意刷:攻击者用脚本高频调用登录/注册/下单接口
爬虫过度抓取:搜索引擎爬虫或采集程序在短时间内发起大量请求,耗尽后端资源
突发流量冲击:促销活动、热点事件导致流量瞬间暴增,后端来不及扩容
单个用户滥用:API 调用频率超出合理范围,影响其他正常用户
Nginx 内置的 ngx_http_limit_req_module(基于漏桶算法)和 ngx_http_limit_conn_module(基于并发连接数)可以满足绝大部分限流需求,无需引入额外的限流组件。
本文从基础配置到生产级最佳实践,涵盖 IP 限流、API Key 限流、白名单绕过、自定义错误响应、灰度验证等场景。
一、核心原理:漏桶算法
Nginx 的 limit_req 模块基于漏桶(Leaky Bucket)算法实现。类比一个底部有小孔的水桶:
rate:水桶底部小孔的流水速率 —— 即允许通过的请求速率
burst:水桶的容量 —— 突发请求的缓冲队列
nodelay/delay:是否让超出 burst 的请求排队等待
关键区别(相比令牌桶):
漏桶强制限流:输出的速率是恒定的,适用于保护后端不被突发流量冲垮
令牌桶允许突发:短期内可用完积累的令牌,适用于需要应对突发流量的场景
Nginx 的 limit_req 属于漏桶实现,但通过 burst + nodelay 参数可以模拟令牌桶的行为。
二、配置指令详解
2.1 核心指令
| 指令 | 作用域 | 说明 |
|---|---|---|
| limit_req_zone | http | 定义共享内存区域和请求速率 |
| 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+ 干跑模式(只统计不限制) |
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 字节,IPv6 占 16 字节,比 $remote_addr(字符串形式的 IP)更节省内存。推荐使用 $binary_remote_addr。
zone=name:size:共享内存名称和大小。64 位系统上每个状态约 128 字节,10MB ≈ 可存储 8 万个客户端状态。
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 突发队列大小
}
zone:指定使用的限流区域名称
burst:允许的突发请求数。当请求超过 rate 后,超出的请求会进入一个队列等待。burst=20 表示队列容量为 20。
nodelay:如果加上 nodelay,队列中的请求不延迟处理,直接放行。如果不加 nodelay,超出 rate 的请求会被延迟处理(即排队等待),导致响应变慢。
delay=N:1.15.7+ 引入的精细控制。前 N 个超出请求不延迟,后续超出请求才延迟。
三、基础配置示例
3.1 基于 IP 限流(最常用)
http {
# 定义限流区域:依据客户端 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 次请求
允许突发 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 变量命名规则——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)关系——必须同时满足所有规则。记录日志时会分别记录每条规则的命中情况。
四、高级配置场景
4.1 delay 参数精细控制(1.15.7+)
nodelay 是"要么直接放行,要么直接拒绝",适合需要严格保护的接口。但过于刚性,可能导致正常用户在突发后瞬间被限。
delay 参数提供了中间状态:超出 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; # 内网段
# 可继续添加其他白名单 IP 段
}
# 根据 $limit 决定限流 key
map $limit $limit_key {
0 ""; # 空 key 不触发限流
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 不会为这个请求创建状态,即不触发限流。
4.3 自定义错误响应
默认限流返回 503 Service Unavailable,但 503 可能被客户端或浏览器重试。推荐统一返回 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; # 直接指向一个静态文件
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;
# 静态资源:宽松
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 — 放行(未触发限流) # DELAYED — 被延迟(超出 rate) # REJECTED — 被拒绝(超出 burst) # DELAYED_DRY_RUN — 干跑模式下被延迟 # REJECTED_DRY_RUN — 干跑模式下被拒绝
五、并发连接数限制(limit_conn)
limit_req 控制的是请求速率(RPS),而 limit_conn 控制的是并发连接数。两者可以配合使用:
http {
# 定义连接数限制区域
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
server {
location /api/ {
# 限制每个 IP 最多 10 个并发连接
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.conf test is successful
6.2 限流效果压测
使用 ab(Apache Bench)模拟高并发:
# 发送 150 个请求,20 并发 $ 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 监控指标
如果使用 Nginx Plus 或 ngx_http_stub_status_module,可以采集限流相关指标。在开源版 Nginx 中,主要通过访问日志和错误日志中的 429 数量来监控限流效果。
# 统计每分钟的 429 数量
$ tail -10000 /var/log/nginx/access.log | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn
七、内存估算
创建 zone 时需要估算内存大小。64 位系统上每个客户端状态占用约 128 字节:
1MB ≈ 8,000 个客户端 10MB ≈ 80,000 个客户端 100MB ≈ 800,000 个客户端
根据业务的实际 IP 数量估算:
普通业务系统(日活 10 万):10MB 足够
面向公众的高流量服务:建议 50~100MB
需要区分 API Key 的场景:zone 大小取决于 API Key 的数量,每个 Key 也是一个独立状态
如果 zone 过小,会出现内存淘汰,导致部分客户端的状态被覆盖——限流将不再准确。
八、局限性与替代方案
8.1 Nginx 原生限流的局限
| 局限 | 说明 | 替代方案 |
|---|---|---|
| 限流状态无法跨节点共享 | 每个 Nginx 节点独立统计,集群下总限流不准 | Redis + Lua、集中式限流网关 |
| 重启后统计清零 | zone 存储在共享内存中,重启 Nginx 后所有计数归零 | 无持久化方案 |
| 无法按时间段限流 | 不支持"每小时 N 次"的滑动窗口 | 自定义 Lua 脚本 |
| 限流粒度有限 | 原生只支持 IP、请求头、URL 等变量作为 key | OpenResty + Lua |
8.2 Redis + Lua 补充方案
如果需要集群级别的分布式限流,可以在 Nginx 中嵌入 Lua 脚本(OpenResty / lua-nginx-module):
-- 简单的分布式滑动窗口限流
local key = "rate_limit:" .. ngx.var.binary_remote_addr
local limit = 10 -- 每秒最大请求数
local window = 1 -- 窗口大小(秒)
local red = redis:new()
red:set_timeout(100)
local ok, err = red:connect("127.0.0.1", 6379)
local current = red:incr(key)
if current == 1then
red:expire(key, window)
end
if current > limit then
ngx.exit(429)
end
8.3 连接数限制模块(limit_conn)的补充
如果限流需求不仅是请求速率,还包括下载带宽限制,需要 ngx_http_limit_conn_module + 第三方的 ngx_http_limit_rate 模块,或者使用 OpenResty。
九、生产环境部署注意事项
限流之前先上线监控:在启用限流前,先确认 Nginx 的 429 状态码已在监控中,否则限流上线后无法评估效果。
先干跑再正式:新上线的限流策略先用 limit_req_dry_run 观察至少一个业务周期,确认不会误伤正常用户后再正式启用。
限流阈值留有余量:不要卡着业务 QPS 的极限设置限流阈值。比如业务单机 QPS 峰值是 1000,限流设置在 1200 并开启 burst,而不是精确地设置 1000。
业务方确认阈值:限流阈值由后端业务方提供,而不是运维随意设定。需要确认正常用户在一次页面操作中会发起多少次 API 请求。
限流不等于拒绝:对于重要接口(支付、下单),限流后应该返回友好的提示信息(如"系统繁忙,请稍后重试"),而不是直接返回一个难看的 429 页面。
限流需要配合降级:限流是保护手段,不是解决方案。如果限流失效(如攻击流量超过 Nginx 处理能力),需要配合 WAF、CDN、DDoS 高防等其他手段组合防御。
十、总结
Nginx 限流配置的核心三要素:
1. limit_req_zone(定义限流规则) → 2. limit_req(应用到指定位置) → 3. limit_req_status(自定义拒绝码)
| 场景 | 推荐配置 |
|---|---|
| 登录接口防刷 | rate=2r/s burst=5 nodelay |
| 普通 API | rate=20r/s burst=30 nodelay |
| 多租户 API | Key 作为 key + 单租户速率配置 |
| 内部系统 | IP 白名单跳过限流 |
| 大促活动 | 临时降低 rate,配合 error_page 返回排队页 |
生产环境的最佳实践:
用 429 而不是 503 表示限流拒绝,方便监控和排查
用 delay=N 参数让突发请求中的小部分被延迟而非直接拒绝
用 $binary_remote_addr 而不是 $remote_addr 作为 key
用 geo + map 模块在白名单 IP 上跳过限流
新策略先干跑模式验证,再正式启用
全部0条评论
快来发表一下你的评论吧 !