Nginx 限流的核心机制

在 Web 服务运维中,限流是个绕不开的话题。不是因为技术多难,而是因为你永远不知道什么时候会被刷。我之前维护的一个内部接口,某天凌晨突然收到了几十万次请求,查了日志才发现是某个爬虫在跑。从那之后,我就养成了上线必配限流的习惯。

Nginx 提供了两种限流模块:limit_reqlimit_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_zonezone=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_fromreal_ip_header)。

遗留问题

  • 如果 Nginx 前面有多层代理(CDN → 负载均衡 → Nginx),如何确保限流的准确性?理论上要在最外层限流,但实际中往往做不到。
  • limit_req 的漏桶算法在高并发下是否有性能瓶颈?官方文档没提,待压测验证。

关联笔记

暂未找到强关联笔记

标签: 服务器, Nginx

添加新评论