Nginx 如何实现限流?有哪些限流策略?
Nginx 如何实现限流?有哪些限流策略?
Nginx 限流的核心思路是控制单位时间内的请求量或并发连接数,防止后端服务被流量打垮。面试中这道题主要考察三个层面:你知道哪些限流模块、你理解底层算法吗、你在生产环境怎么用。
限流的两种基本方式
Nginx 提供两大限流模块:
limit_req:限制请求速率,控制单位时间内允许的请求数limit_conn:限制并发连接数,控制同一时刻的 TCP 连接数
两者的区别在于粒度——limit_req 关注的是请求频率(每秒多少个请求),limit_conn 关注的是连接数(同时存在多少个连接)。一个长连接可以承载多个请求,所以实际防护中通常两者配合使用。
limit_req:请求速率限制
nginxhttp { limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; server { location /api/ { limit_req zone=api_limit burst=20 nodelay; limit_req_status 429; proxy_pass http://backend; } } }
关键参数解读:
$binary_remote_addr:以客户端 IP 作为限流键,二进制格式比字符串格式节省内存,10MB 共享内存大约能记录 16 万个 IPzone=api_limit:10m:定义共享内存区域名称和大小rate=10r/s:每秒允许 10 个请求,也可以用r/m表示每分钟burst=20:允许 20 个突发请求排队等待nodelay:突发请求不延迟处理,超出 burst 容量则直接拒绝limit_req_status 429:被限流时返回 429 而非默认的 503
burst 和 nodelay 到底怎么配合?
这是面试的高频追问,很多人配置过但说不清楚原理。
只用 limit_req zone=api_limit:严格按 rate 执行,超出的请求直接 503,体验差。
加 burst=20:允许 20 个请求排队,Nginx 按 rate 速率逐个处理队列中的请求,多余请求延迟响应。好处是不误杀,坏处是用户感知延迟。
再加 nodelay:队列中的请求立即处理,不延迟响应,但队列满了还是拒绝。实际效果是「短时间内允许突发,超出就拒绝」,适合大多数 API 场景。
简单记:burst 控制能容忍多少突发,nodelay 决定突发请求是延迟还是立即处理。
limit_conn:并发连接数限制
nginxhttp { limit_conn_zone $binary_remote_addr zone=conn_limit:10m; server { limit_conn conn_limit 10; proxy_pass http://backend; } }
这里 limit_conn conn_limit 10 表示同一个 IP 最多同时保持 10 个连接。注意这和 limit_req 不同——limit_req 限制的是请求的到达速率,limit_conn 限制的是连接的并发数量。
典型场景:防止单个客户端通过大量并发连接耗尽服务器资源(如慢速攻击 Slowloris)。
底层算法:漏桶与令牌桶
面试中问限流,必然会追问算法原理。
漏桶算法(Leaky Bucket)
请求像水一样倒入桶中,桶以固定速率漏水。如果桶满了,新请求被丢弃。特点是输出速率恒定,不管输入多猛烈,处理速度始终平稳。limit_req 不加 burst 参数时就是典型的漏桶行为——严格按 rate 处理,超出直接拒绝。
令牌桶算法(Token Bucket)
系统以固定速率往桶里放令牌,每个请求需要取走一个令牌。桶满了令牌不再增加。与漏桶的区别在于:令牌桶允许突发——桶里攒够了令牌时,可以一次性处理一批请求。limit_req 加上 burst 参数就实现了类似令牌桶的效果,允许一定程度的流量突发。
核心区别:漏桶强制匀速输出,令牌桶允许有限突发。Nginx 的 limit_req 实际上是两者的结合——基础速率是漏桶,burst 提供了令牌桶式的突发能力。
带宽限制
除了请求和连接层面的限流,Nginx 还能限制响应传输速率:
nginxlocation /download/ { limit_rate 1m; limit_rate_after 10m; root /var/www/files; }
limit_rate 1m:限速 1MB/slimit_rate_after 10m:前 10MB 不限速,之后才限速
适用于大文件下载场景,防止少数大流量用户占满带宽。
白名单与动态限流
生产环境中通常需要对内部 IP 或特定请求方法豁免限流。
基于 geo 的白名单:
nginxgeo $limit_key { default $binary_remote_addr; 192.168.1.0/24 ""; 10.0.0.0/8 ""; } limit_req_zone $limit_key zone=whitelist:10m rate=10r/s;
白名单内的 IP 对应空字符串,不参与限流计算。
基于请求方法的动态限流:
nginxmap $request_method $limit_key { default $binary_remote_addr; GET ""; HEAD ""; }
GET 和 HEAD 请求不限流,其他方法(POST、PUT 等)参与限流,适合写操作需要更严格控制的场景。
多层限流实战配置
nginxhttp { # 全局限流 limit_req_zone $binary_remote_addr zone=global:10m rate=50r/s; # API 接口限流 limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; # 登录接口限流 limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; # 连接数限制 limit_conn_zone $binary_remote_addr zone=conn:10m; limit_req_status 429; limit_conn_status 429; server { limit_conn conn 20; location / { limit_req zone=global burst=50 nodelay; proxy_pass http://backend; } location /api/ { limit_req zone=global burst=50 nodelay; limit_req zone=api burst=10 nodelay; proxy_pass http://api_backend; } location /login { limit_req zone=login burst=2 nodelay; proxy_pass http://auth_backend; } } }
这套配置的思路是分层防护:全局兜底防 DDoS,API 层控制接口频率,登录接口单独严控防暴力破解。同一个 location 可以叠加多个 limit_req,任一规则触发都会拒绝请求。
限流日志与监控
nginxlog_format limit '$remote_addr - [$time_local] "$request" ' '$status limit=$limit_req_status'; limit_req_log_level warn;
$limit_req_status 变量记录限流状态,limit_req_log_level 控制限流日志级别。生产环境建议用 warn 级别,避免日志量过大。配合 ELK 或 Prometheus 可以做限流趋势分析和告警。
生产环境的几个经验
- 阈值不是拍脑袋定的——先压测后端服务的极限 QPS,限流值设在其 70%-80% 作为安全水位
- 429 响应要友好——返回 JSON 格式的错误提示,带上 Retry-After 头告诉客户端多久后重试
- zone 内存别省——10MB 约存 16 万 IP,如果用户量大要相应调大,内存耗尽后新请求直接 503
- burst 要结合业务——API 类场景 burst 可以小一些(5-10),页面访问场景可以大一些(20-50)
- 限流不是万能的——在 Nginx 层限流只能防住从外到内的流量冲击,内部服务间的调用保护需要 Sentinel 或熔断器
- 关注误杀——公司出口 IP 共享场景下,单 IP 限流会误伤同一 NAT 后的多个用户,可考虑基于 token 或租户维度的限流键
追问:limit_req 和 limit_conn 该选哪个?
都要用。limit_req 防高频请求冲击,limit_conn 防连接数耗尽,两者解决不同问题。面试中如果只答一个,会被认为理解不全面。
追问:Nginx 限流有什么局限?
- 单机维度的限流,分布式环境下需要 Redis + Lua 或专门的限流服务
- 限流键有限,复杂业务逻辑(如按用户等级限流)需要结合 OpenResty 或网关层
limit_req基于共享内存,重启后状态丢失- 不支持滑动窗口计数,只有固定时间窗口的速率计算