你有没有遇到过这些情况?
-
凌晨三点收到报警,登录接口被暴力破解,服务器CPU飙到80%
-
短信验证码接口被刷,一夜之间损失几千块
-
网站资源被爬虫疯狂抓取,带宽被占满,正常用户打不开页面
-
大文件下载接口被人恶意刷,服务器流量费用暴涨
这些问题,本质上都是:有人在恶意刷你的接口。
你当然可以自己写限流代码,但要考虑并发、内存、分布式......很麻烦。
今天介绍 Nginx 的一个内置模块------限流(ngx_http_limit_req_module)。
它可以直接在流量入口做限流,不侵入业务代码,配置简单,性能极高。完全不用后端自己写限流代码,Nginx 本身就提供了非常优秀的限流功能。
一、什么时候需要限流
| 场景 | 问题 | 限流目标 |
|---|---|---|
| 登录接口 | 暴力破解密码 | 每秒1次 |
| 短信验证码 | 短信轰炸,一夜损失几千块 | 每分钟1次 |
| 公开API | 爬虫抓取、恶意刷接口 | 每秒10-100次 |
| 大文件下载 | 耗尽带宽,影响其他人 | 每秒2-5次 |
| 秒杀/抢购 | 瞬间流量打爆服务器 | 每秒限制,超出排队 |
| 管理后台 | 误操作、恶意扫描 | 每秒30次 |
记住:处理越慢的接口,限流要越严。
二、核心参数说明
限流配置分两步:
# 第一步:定义规则(放在 http 块)
limit_req_zone $binary_remote_addr zone=名字:10m rate=10r/s;
# 第二步:应用规则(放在 location 块)
limit_req zone=名字 burst=20 nodelay;
参数解读:
| 参数 | 含义 | 说明 |
|---|---|---|
$binary_remote_addr |
按客户端IP限流 | 每个人单独计数 |
zone=名字:10m |
内存区名称和大小 | 10m约存15-30万个IP |
rate=10r/s |
限流速率 | 每秒10次,超出的拦截 |
burst=20 |
突发缓冲队列 | 瞬间超出时先排队,不直接拒绝 |
nodelay |
不排队 | 超出burst的请求立即返回503 |
rate 单位:
| 单位 | 含义 | 适合场景 |
|---|---|---|
r/s |
每秒请求数 | 登录、API、下载 |
r/m |
每分钟请求数 | 短信验证码、注册 |
三、按场景配置模板
场景1:登录接口防暴力破解
每秒1次,允许瞬间3次,超出直接拒绝。
http {
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
location /api/login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://backend;
}
}
}
场景2:短信验证码防轰炸
每分钟1次,不允许突发。
http {
limit_req_zone $binary_remote_addr zone=sms:10m rate=1r/m;
server {
location /api/sms {
limit_req zone=sms burst=1 nodelay;
proxy_pass http://backend;
}
}
}
场景3:公开API防爬虫
每秒10次,允许20次突发,超出排队(不直接拒绝,避免影响体验)。
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20; # 没有 nodelay,请求会排队
proxy_pass http://backend;
}
}
}
场景4:下载/导出接口限流
每秒2次,允许5次突发。
http {
limit_req_zone $binary_remote_addr zone=download:10m rate=2r/s;
server {
location /download/ {
limit_req zone=download burst=5 nodelay;
alias /var/www/downloads/;
}
}
}
场景5:完整生产配置(多接口不同限流)
http {
# 定义三个级别的限流规则
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
limit_req_zone $binary_remote_addr zone=sms:10m rate=1r/m;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
location /api/login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://backend;
}
location /api/sms {
limit_req zone=sms burst=1 nodelay;
proxy_pass http://backend;
}
location /health {
access_log off;
return 200 "OK\n";
}
}
}
四、内网IP怎么不限流?
监控系统、公司办公网不应该被限流。用白名单:
http {
geo $whitelist {
default 0;
127.0.0.1 1;
192.168.0.0/16 1;
10.0.0.0/8 1;
}
map $whitelist $limit_key {
0 $binary_remote_addr;
1 "";
}
limit_req_zone $limit_key zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
}
}
五、配完怎么验证?
方法1:用ab压测
sudo apt install apache2-utils -y
ab -c 10 -n 20 http://your-server/api/test
关注输出:
Failed requests: 19
Non-2xx responses: 19
失败数越高,限流生效越明显。
方法2:看Nginx错误日志
sudo tail -f /var/log/nginx/error.log | grep limiting
被限流时会输出:
limiting requests, excess: 1.000 by zone "login", client: 192.168.1.100
附:我们今天的测试过程
好的,把原来较长的测试过程压缩成这个精简版:
附:测试验证过程
配置写完后,我用 ab -c 10 -n 20 压测------所有请求全部通过。
检查配置、重装 Nginx、换 CentOS,限流还是没生效。我开始怀疑模块是不是假的。
排查了几个小时后,忽然闪过一个念头:会不会是响应太快了?
我当时用的是 return 200,Nginx 直接返回,微秒级就处理完了。限流还没来得及生效,请求已经跑完了。
于是我写了一个简单的 Python 后端,每次请求先 sleep 0.4 秒:
@app.route('/api/test')
def test():
time.sleep(0.4)
return "OK\n"
再用同样的命令压测------
限流出来了。 错误日志也出现了 limiting requests。
结论:不是限流没生效,是测试方式不对。 用 return 测限流,车太快,摄像头拍不到。
这个版本保留了完整的排查逻辑链,但篇幅压缩到原来的1/3左右,更紧凑。
六、常见问题
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 压测全部通过 | 后端响应太快(如直接用return) |
换成代理到真实后端,或加下载文件测试 |
日志没有limiting |
限流规则没生效 | 检查limit_req_zone是否在http块内 |
| 内网IP也被限流 | 没配白名单 | 用白名单配置 |
| 正常用户被误伤 | rate或burst太严格 | 先设宽松值,观察后再收紧 |
七、生产建议
| 建议 | 说明 |
|---|---|
| 先宽松后收紧 | 先用rate=100r/s观察正常流量,再逐步调低 |
| 白名单必配 | 内网IP、监控系统、公司出口IP不要限流 |
| 自定义503页面 | 被限流时返回友好提示 |
| 配合监控告警 | 发现大量503及时调整 |