Nginx limit_req 限流配置:zone、burst 与 nodelay - 夜莺博客

Nginx limit_req 限流配置:zone、burst 与 nodelay

限流是保护后端最便宜的手段之一,而 Nginx 的 limit_req 模块几乎零成本可用。它的实现是"漏桶"算法:请求以固定速率被处理,超出的请求先排队,队列满了才拒绝。配置只有两行,但 burst、nodelay、delay 三个参数的行为差异非常大,配错会出现"限流形同虚设"或"正常用户被误伤"两种相反的结果。本文把参数语义和实际场景讲清楚。

两行配置的基本结构

限流分两部分:在 http 块里用 limit_req_zone 定义限流键和共享内存区,然后在 server 或 location 里用 limit_req 应用它。

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    server {
        location /search/ {
            limit_req zone=one burst=5;
        }
    }
}

这里创建了名为 one 的 10MB 共享内存区,以客户端 IP 为键。10m 大约可以保存 16 万个 IP 的状态,计算依据是每个键约 64 字节(32 位平台)或 128 字节(64 位平台),与状态数量成正比,与限流速率无关。

建议使用 $binary_remote_addr 而不是 $remote_addr:前者是二进制格式,占用固定 4 字节(IPv6 为 16 字节),后者是变长字符串,会在共享内存里大量浪费空间。

rate 的写法

速率只支持每秒(r/s)和每分钟(r/m)两种单位,不支持每小时。低于 1r/s 的速率用 r/m 表达,例如每 2 秒 1 个请求写成 30r/m。

limit_req_zone $binary_remote_addr zone=perip:10m  rate=1r/s;
limit_req_zone $server_name        zone=pervhost:10m rate=10r/s;

可以同时定义多个 zone,例如限制单 IP 速率的同时限制整个虚拟主机的总速率。同一层级(http / server / location)可以写多条 limit_req,每条都是独立的限制,全部生效。

burst、nodelay 与 delay 的区别

这是最容易配错的部分。

  • 不带 burst:队列容量为 0,任何超过速率的请求直接以 503 拒绝。速率 1r/s 意味着同一秒内的第二个请求就会被拒。
  • burst=5:允许 5 个超出速率的请求排队,它们会被延迟处理以保证整体速率不超过设定值。第 6 个请求开始被拒绝。
  • burst=5 nodelay:队列里的请求立即被处理,不延迟,但超出 burst 的仍然拒绝。这适合"允许突发但不允许持续超过平均速率"的场景。
  • burst=5 delay=3:前 3 个请求不延迟直接处理,之后到第 5 个之间的请求开始被延迟。这是 nodelay 和完全延迟之间的折中。
limit_req zone=one burst=5 nodelay;
limit_req zone=one burst=5 delay=3;

直观理解:nodelay 保护的是平均速率而不是瞬时速率,适合页面加载需要并发拉取多个资源的正常用户;纯延迟模式则会让用户体验明显变慢,只适合对速率极度敏感的接口。

拒绝时的返回码与日志

超限请求默认返回 503,可以改成 429 以符合 HTTP 语义,同时调整日志级别避免刷爆 error_log:

limit_req_status 429;
limit_req_log_level warn;
limit_req_dry_run on;      # 只记录不拒绝,用于上线前评估影响

limit_req_dry_run 非常值得用于灰度:先观察日志中"本应被限流"的请求量,确认不会误伤正常流量后再关闭 dry run。

按接口而非按 IP 限流

对已登录的 API,按 IP 限流往往不合理(大量用户共享出口 IP)。可以改用用户标识作为键:

limit_req_zone $http_authorization zone=api_user:10m rate=20r/s;
map $http_x_api_key $limit_key {
    default        $http_x_api_key;
    ""             $binary_remote_addr;   # 没有 key 时退回按 IP
}

map 可以让限流键根据请求内容动态选择,这是生产环境里最实用的技巧之一。

验证

nginx -t
nginx -s reload
# 压测验证限流是否生效
wrk -t2 -c10 -d10s http://127.0.0.1/search/
ab -n 200 -c 20 http://127.0.0.1/search/
tail -f /var/log/nginx/error.log | grep "limiting requests"

error_log 里出现 limiting requests, excess: ... by zone 说明限流确实在工作。如果压测完全没有触发限流,先确认 limit_req 写在了正确的层级 — 写在 server 块里会影响该虚拟主机的所有请求,写在 location 里才只针对特定路径。

本站在反向代理相关主题上还有 Nginx Proxy Manager: Reverse Proxy and SSL in the GUI 基础配置、Traefik Reverse Proxy with Automatic Let's Encrypt TLS 的动态配置对比,以及 Caddy Reverse Proxy: Automatic HTTPS for Network Services 自动 HTTPS 的替代方案,可结合阅读。

原文链接:https://nginx.org/en/docs/http/ngx_http_limit_req_module.html