
字节笔记本
2026年10月6日 · 约 9 分钟读完
Nginx 请求限流实战:场景、参数与最佳实践
在高并发场景下,请求限流是保护服务的第一道闸门。突发流量一旦涌入,缺少限流手段的服务往往依次出现负载飙升、响应变慢、正常用户被挤出,最坏情况下整台服务宕机。Nginx 自带的 limit_req 模块用几十行配置就能把这波洪峰挡在门外,本文把配置参数、典型场景与运维要点整理成一套可以直接落地的做法。
一、先理解三个核心指令
限流的全部逻辑建立在三条指令上:
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /api/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_status 429;
}
}limit_req_zone定义一块共享内存区。$binary_remote_addr表示按客户端 IP 的二进制形式作为限流 key,比字符串形式更省内存;zone=mylimit:10m分配 10MB 共享内存存放状态;rate=10r/s是允许的长期平均速率。limit_req在具体 location 上引用这块区域。burst=20表示允许 20 个请求的突发额度;nodelay让突发额度内的请求立即处理,不排队、不人为延迟。limit_req_status 429把超限响应码定为 429。默认的 503 容易与后端故障混淆,429 的语义就是"请求过多",便于监控与客户端重试逻辑区分处理。
一条请求进来后的完整判定流程如下图:速率内直接放行;超速时先看 burst 是否还有余量,有余量且配置了 nodelay 就立即处理;配额用尽则返回 429,并可通过 error_page 转到自定义响应。

二、场景一:抢购详情页,宽速率加大突发
春节大促期间,单个商品页面的峰值 QPS 可能上万。这类场景不怕流量大,就怕流量把后端打垮,策略是给足均值速率和突发额度:
location /product/ {
limit_req_zone $binary_remote_addr zone=product:10m rate=200r/s;
limit_req zone=product burst=1000 nodelay;
proxy_pass http://product_backend;
}每秒 200 次的均值配上 1000 的突发额度,秒杀瞬间的点击洪峰被 Nginx 吸收削平,到达后端的是相对平滑的流量。
三、场景二:验证码接口,严格限流防刷
验证码接口一旦被脚本刷量,短信或邮件成本会直接失控。这类接口要卡得非常紧:每秒 1 次、最多突发 3 次,超限直接拒绝并返回结构化提示。
location /api/captcha/ {
limit_req_zone $binary_remote_addr zone=captcha:10m rate=1r/s;
limit_req zone=captcha burst=3 nodelay;
limit_req_status 429;
error_page 429 = @too_many_requests;
}
location @too_many_requests {
default_type application/json;
return 429 '{"code": 429, "message": "Too Many Requests", "error": "请求过于频繁,请稍后再试"}';
}把错误响应约定成 JSON,前端拿到 code 就能弹出友好提示,引导用户调整操作频率,而不是面对一个裸的 503 页面。
四、场景三:按用户等级差异化限流
不同等级的用户本就该有不同的配额。借助 map 把请求头里的用户等级映射成不同速率,同一个 location 就能实现分级限速:
map $http_x_user_level $api_limit_rate {
default "10r/s";
"vip" "50r/s";
"svip" "100r/s";
}
limit_req_zone $binary_remote_addr zone=api:20m rate=$api_limit_rate;普通用户 10r/s,vip 50r/s,svip 100r/s,付费用户的体验得到保障,免费额度也不被滥用。
五、三个进阶玩法
多维度限流。 IP 与用户 ID 各建一个 zone,在同一个 location 里同时生效,防止单人换 IP 绕过限制:
limit_req_zone $binary_remote_addr zone=ip:10m rate=10r/s;
limit_req_zone $uid zone=user:10m rate=5r/s;
location /api/ {
limit_req zone=ip burst=10 nodelay;
limit_req zone=user burst=5 nodelay;
}白名单放行。 用 geo 配合 map,让内网网段和监控探针不受限流影响:
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/16 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=mylimit:10m rate=10r/s;命中白名单时 key 为空字符串,Nginx 对空 key 的请求不计入限流,探针和内部调用得以畅通。
动态调整速率。 固定参数应付不了大促临时扩容、故障降级这类变化,这时可以走动态化路线:在 OpenResty 环境里用 Lua 脚本从 Redis 读取当前的限流配置,请求阶段按最新值执行拦截,运维改一个 Redis 键就能全集群生效。不同版本的扩展模块接口差异较大,落地前需按所用环境验证。
六、监控与日志
限流上线后必须能看见它的工作状态:
location /nginx_status {
stub_status on;
access_log off;
}
map $limit_req_status $loggable {
429 1;
default 0;
}
access_log /var/log/nginx/limit.log combined if=$loggable;stub_status 暴露连接数等基础指标;下面的写法只在请求被限流时记一条日志,日志文件里全是 429,统计被限流量、定位被刷接口都非常直接。
七、最佳实践清单

- 容量规划。 zone 的 1MB 内存大约能存 16000 个 IP 的状态,估算峰值独立 IP 数后按 2 倍余量设置,避免内存不足导致限流失效。
- 分级限流。 全局限流兜底,接口级限流针对热点路径,用户级限流实现差异化,三层叠加才能既保住系统又不误伤正常业务。
- 监控告警。 对 429 状态码单独建监控,配合限流日志分析设置告警阈值,流量异常时第一时间知道是哪个接口在承压。
- 定期调优。 限流参数没有万能值,周期性分析访问日志,用压测验证新配置,必要时做 A/B 对比,让参数跟随业务演进。
结语
请求限流是花小钱防大事故的典型手段:几行配置,换来的是突发流量下的服务稳定性。建议每个上线前的接口都过一遍限流设计,先在压测环境找到适合自己业务量级的参数组合,再带着监控和日志上线,让每一次拦截都看得见、可解释。



