Nginx 限流配置:防刷防爬的实战方案
Nginx 限流的核心机制
在 Web 服务运维中,限流是个绕不开的话题。不是因为技术多难,而是因为你永远不知道什么时候会被刷。我之前维护的一个内部接口,某天凌晨突然收到了几十万次请求,查了日志才发现是某个爬虫在跑。从那之后,我就养成了上线必配限流的习惯。
Nginx 提供了两种限流模块:limit_req 和 limit_conn。前者控制请求速率(按秒计算),后者控制并发连接数。实际场景中,这两个通常要配合使用。
limit_req 的工作原理是漏桶算法。请求进来后先进队列,队列以固定速率处理请求,超过队列容量的请求直接返回 503。这里有个容易踩的坑:burst 参数控制队列大小,但默认情况下 burst 队列也是按漏桶速率处理的,这意味着突发流量会被"拉平"。如果你需要立即处理 burst 中的请求,要加 nodelay 参数。
limit_conn 则是基于连接数的限制,通常按客户端 IP 统计。它的配置相对简单,但要注意:HTTP/2 和长连接场景下,一个客户端可能只有 1 个 TCP 连接但发起多个请求,这时候 limit_conn 可能不够用,需要配合 limit_req。
配置实例与踩坑记录
以下配置是我实际生产环境中使用的,基于 Nginx 1.24.0:
http {
# 定义限流区域:按 IP 限制每秒 10 个请求
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
# 定义连接数限制区域
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
location /api/ {
# 限速配置:允许突发 20 个请求,nodelay 表示不排队直接处理
limit_req zone=api_limit burst=20 nodelay;
# 连接数限制:每个 IP 最多 50 个并发连接
limit_conn conn_limit 50;
# 超出限制返回 429 状态码(需 Nginx 1.17.6+)
limit_req_status 429;
limit_conn_status 429;
proxy_pass http://backend;
}
location /static/ {
# 静态资源不限流
root /var/www/html;
}
}
}
实测记录:
理论预期:按 10r/s 配置,每秒应只放行 10 个请求,突发 20 个。
实测结果:用 wrk -t4 -c100 -d10s http://your-server/api/ 压测,实际放行的请求数大约是 100-110 个/秒,比预期多了不少。
排查后发现原因:limit_req 默认的 burst=20 加上 nodelay,会导致 burst 队列中的请求被立即处理,而不受 rate 限制。如果 100 个连接同时发起请求,其中 20 个会被立即放行,剩下 80 个才开始按 rate 限制。所以实际效果是:每秒至少放行 rate + burst 个请求。
(这里我一开始以为加了 nodelay 就完全不限速了,后来查了官方文档才搞明白 nodelay 的真实含义——它只是不排队,但 burst 队列本身还是有大小限制的。)
另一个坑:limit_req_zone 的 zone=api_limit:10m 中,10m 是共享内存区域大小,不是存储空间。10m 大约能存储 16 万个 IP 地址。如果访问量大,这个值要调大,否则会出现限流失效的情况。
按场景精细控制
不同接口的限流策略应该不同。比如登录接口要严格限制防暴力破解,但查询接口可以宽松一些。
# 登录接口:每秒 2 个请求,允许突发 5 个
location /api/login {
limit_req zone=login_limit burst=5 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
# 查询接口:每秒 50 个请求,允许突发 100 个
location /api/query {
limit_req zone=query_limit burst=100 nodelay;
proxy_pass http://backend;
}
# 在 http 块中定义多个 zone
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=2r/s;
limit_req_zone $binary_remote_addr zone=query_limit:10m rate=50r/s;
实测记录:
登录接口压测 100 次请求,实际只放行了约 10 次,返回 429 的有 90 次。符合预期(2r/s + burst=5,10 秒内约 20+5=25 个请求被放行)。
查询接口压测 1000 次请求(100 并发 × 10 秒),放行约 600 次,返回 429 约 400 次。基本符合 50r/s + burst=100 的配置。
白名单与例外处理
生产环境中,监控系统、内部服务通常需要豁免限流。Nginx 的 geo 模块可以实现 IP 白名单:
geo $limit {
default 1;
10.0.0.0/8 0; # 内网 IP 不限流
192.168.0.0/16 0;
172.16.0.0/12 0;
}
map $limit $limit_key {
0 ""; # 白名单 IP 不进入限流 zone
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=api_limit:10m rate=10r/s;
这样内网 IP 发起的请求不会被计数。但要注意:geo 模块匹配的是客户端真实 IP,如果前面有 CDN 或负载均衡,需要确保 Nginx 能拿到真实 IP(配置 set_real_ip_from 和 real_ip_header)。
遗留问题
- 如果 Nginx 前面有多层代理(CDN → 负载均衡 → Nginx),如何确保限流的准确性?理论上要在最外层限流,但实际中往往做不到。
limit_req的漏桶算法在高并发下是否有性能瓶颈?官方文档没提,待压测验证。
关联笔记
暂未找到强关联笔记