Skip to content

Nginx限流、排队与拒绝全过程

限流不是“流量大就返回503”。一个可用的限流方案必须回答:按谁限、平均速率是多少、允许多大突发、突发是立即放行还是延迟、超过阈值返回什么、多个Worker是否共享、多个Nginx实例是否共享,以及限流失败时保护谁、误伤谁。

学习目标

学完后应能:

  1. 区分速率限制、并发限制、连接限制和业务配额。
  2. 解释 limit_req_zone 共享内存中的Key、时间和超额状态。
  3. 用时间线推导rate、burst、nodelay和delay。
  4. 说明burst是可容纳的超额请求数量,不是“每秒额外额度”。
  5. 解释nodelay立即放行后为什么仍然产生速率债务。
  6. 区分limit_req漏桶式整形和令牌桶允许突发的方式。
  7. 解释limit_conn何时计数,以及HTTP/2/3与WebSocket边界。
  8. 正确处理真实IP、NAT、CDN、租户和用户Key。
  9. 说明单机共享zone与多Nginx实例全局限流的区别。
  10. 为登录、短信、搜索、上传和Webhook设计分层商业策略。

一、限流究竟保护什么

mermaid
flowchart TD
    A["客户端流量"] --> B["Nginx入口限流"]
    B --> C["应用线程池"]
    C --> D["数据库连接池"]
    D --> E["MySQL、Redis、MQ和外部接口"]

入口限流的目标通常是保护最慢、最贵或最脆弱的共享资源:

  • 登录密码校验CPU和风控服务。
  • 短信供应商额度与成本。
  • 搜索集群CPU和复杂查询。
  • 文件上传带宽、磁盘和对象存储。
  • Java线程池和数据库连接池。
  • 支付、库存等核心链路。

如果不知道要保护的资源容量,rate=100r/s只是猜测。

二、四类限制先分清

类型限制对象典型指标Nginx能力
请求速率一段时间进入多少请求r/s、r/mlimit_req
并发请求同时处理多少请求in-flight requestslimit_conn可近似部分场景
TCP连接同时多少网络连接established connections还需系统、防火墙、stream等层治理
业务配额用户每天可发几条短信次/日、金额/日通常应由业务/网关状态服务实现

Nginx的IP速率限制不能代替用户配额。例如同一用户换IP后,按IP计数会重新开始;同一公司NAT下多人又可能共享一个IP被误伤。

三、limit_req由两类指令组成

在http层定义共享状态区:

nginx
limit_req_zone $binary_remote_addr zone=login_ip:20m rate=5r/s;

在server或location中应用:

nginx
location = /api/login {
    limit_req zone=login_ip burst=10 nodelay;
    proxy_pass http://auth_backend;
}

分工:

  • limit_req_zone:定义Key、共享内存区名称和大小、平均速率。
  • limit_req:把某个zone应用到请求路径,并配置burst、delay/nodelay。

只定义zone但不应用,路径不会被限制;只写limit_req但没有同名zone,配置检查会失败。

四、共享内存中保存什么

对每个非空Key,Nginx需要保存类似:

  • Key值或其紧凑表示。
  • 最近请求时间。
  • 当前超额/欠债状态。
  • 红黑树等索引节点元数据。
  • 内存管理开销。
mermaid
flowchart TD
    A["请求到达"] --> B["计算限流Key"]
    B --> C{"Key是否为空"}
    C -- "是" --> D["通常不参与该zone计数"]
    C -- "否" --> E["在共享内存查找Key状态"]
    E --> F["按经过时间衰减旧的超额量"]
    F --> G["加入当前请求的消耗"]
    G --> H["判断立即、延迟或拒绝"]
    H --> I["更新Key状态"]

共享内存让同一Nginx实例的多个Worker看到同一zone状态,避免每个Worker各给一份额度。

五、rate的准确含义

nginx
rate=10r/s

表示长期平均速率每秒10个请求,可换算为平均100ms消化一个请求的超额量:

text
1秒 / 10 = 100毫秒

它不表示:

  • 每个自然秒零点重置10张票。
  • 任意1秒窗口绝对只能看到10个请求。
  • 后端一定每100ms完成一个请求。
  • QPS容量就是10。

Nginx根据请求到达时间连续计算状态,不依赖自然秒边界。

六、不配置burst时发生什么

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

location /api/ {
    limit_req zone=one;
}

假设同一个Key在短时间发送多个请求:

text
t=0ms    请求A到达:允许
t=100ms  请求B到达:距离下一个平均额度还很早,拒绝
t=500ms  请求C到达:仍可能超额,拒绝
t=1000ms 请求D到达:速率债务大致消化,可允许

精确边界受实现的时间精度和状态计算影响,但核心是:没有burst时几乎不容纳突发超额请求。

七、burst到底是什么

nginx
limit_req zone=one burst=5;

burst=5表示每个Key最多容纳一定数量的超额请求状态。默认情况下,这些超额请求会被延迟,使它们按rate逐步获得处理时机;超过burst容量的请求被拒绝。

它不是:

  • 每秒额外5个永久额度。
  • 后端并发上限5。
  • 队列最多占5个线程。
  • 请求一定在5秒内完成。

延迟中的请求仍占用客户端连接、Nginx请求对象、内存和定时器。大量排队会增加入口连接和用户延迟,不能把Nginx当无限消息队列。

八、burst默认延迟的时间线

配置:

nginx
limit_req_zone $binary_remote_addr zone=one:10m rate=2r/s;

location /api/ {
    limit_req zone=one burst=3;
}

平均每500ms消化一个请求。若同一Key瞬间到达4个请求,可用简化模型理解:

text
请求A:立即处理
请求B:延迟到约500ms
请求C:延迟到约1000ms
请求D:延迟到约1500ms
再来请求E:超过burst,拒绝

实际边界取决于请求到达微小时间差和实现计算。这个时间线用于理解“整形”,不是承诺精确调度时刻。

mermaid
flowchart TD
    A["瞬时请求"] --> B["第一个直接通过"]
    A --> C["burst内超额请求进入延迟状态"]
    C --> D["按rate逐步唤醒"]
    A --> E["超过burst"]
    E --> F["立即拒绝"]

九、nodelay不是免费突发额度

nginx
limit_req zone=one burst=5 nodelay;

nodelay让burst范围内的超额请求立即放行,不等待rate节奏,但这些请求仍被计入超额状态,形成“速率债务”。之后债务按rate随时间消化。

简化时间线,rate=1r/s burst=5 nodelay

text
t=0瞬间:请求A和最多5个超额请求可立即通过
紧接着的新请求:burst状态已占满,会被拒绝
之后每经过约1秒:债务逐步减少,重新腾出一部分突发空间

所以nodelay可能把瞬时压力直接交给后端。只有后端确实能承受该突发并发时才使用。

十、delay参数如何分两段处理突发

nginx
limit_req zone=one burst=12 delay=8;

可以理解为:

  • 一部分超额请求在delay阈值范围内立即通过。
  • 超过delay但仍在burst范围内的请求按rate延迟。
  • 超过burst的请求拒绝。
mermaid
flowchart TD
    A["请求到达并计算超额量"] --> B{"在正常速率内"}
    B -- "是" --> C["立即处理"]
    B -- "否" --> D{"超额量是否不超过delay"}
    D -- "是" --> E["立即处理但记录债务"]
    D -- "否" --> F{"是否仍在burst内"}
    F -- "是" --> G["延迟到rate允许的时刻"]
    F -- "否" --> H["拒绝"]

不同Nginx版本对 delay= 的支持时间不同,必须在目标版本执行 nginx -Vnginx -t

十一、nodelay与delay的关系

概念上:

  • 默认:burst内超额请求都延迟。
  • delay=N:前N份超额可立即通过,之后到burst之间延迟。
  • nodelay:burst范围内超额都立即通过,可理解为不对burst内请求做延迟。

它们改变的是突发请求的放行时间,不会把长期平均rate改大。

十二、为什么不能只看平均QPS

假设后端长期可处理100r/s,但瞬时只能承受200个并发。若配置:

nginx
rate=100r/s burst=500 nodelay

500个超额请求可能瞬间进入后端,压垮线程池和数据库连接池。平均rate正确,突发设计仍然错误。

必须同时评估:

  • 长期安全吞吐。
  • 瞬时安全并发。
  • 平均响应时间。
  • P99。
  • Java线程池和队列。
  • 数据库连接池。
  • 单请求内存与外部调用。

十三、漏桶和令牌桶不是完全等价

漏桶式整形

text
请求进入桶或形成超额状态
→ 按稳定速率流出
→ 桶满后拒绝

优势是输出更平滑,适合保护不能承受突发的后端。

令牌桶

text
按固定速率生成令牌
→ 空闲时令牌可积累到桶容量
→ 请求消耗令牌
→ 有积累令牌时允许突发
→ 令牌不足时等待或拒绝

令牌桶天然允许使用历史空闲积累突发;经典漏桶输出更趋向恒定。二者都能做限速,但状态含义和突发表现不同,不能说“底层逻辑完全一样”。

Nginx limit_req通常用漏桶式/超额量模型解释更准确,burst+nodelay让它具备立即接受一定突发的行为,但仍不等于一个会长期积累空闲令牌的标准令牌桶。

十四、limit_req的状态码和日志

生产API通常希望超限返回429,而不是默认503:

nginx
limit_req_status 429;
limit_req_log_level warn;

429表示Too Many Requests,更符合客户端语义。是否添加 Retry-After、返回JSON错误体、客户端如何退避,要结合API规范实现。

可配置dry run观察而不真正拒绝:

nginx
limit_req_dry_run on;

版本支持需验证。Dry run阶段要记录命中量、Key分布、误伤用户和预期状态,不能开启后无人分析。

十五、limit_req状态变量

现代版本可通过类似 $limit_req_status 的变量记录请求状态,例如通过、延迟、拒绝或dry-run结果,具体值以版本文档为准。

日志示例:

nginx
log_format limit_main
    '$request_id $remote_addr "$request" status=$status '
    'limit_req=$limit_req_status request_time=$request_time';

只有总429数量不够,还要按:

  • URI。
  • Key维度。
  • 客户端类型。
  • 租户。
  • 版本。
  • 上游响应时间。

分析是否遭受攻击、真实高峰或配置误伤。

十六、zone容量怎么规划

nginx
limit_req_zone $binary_remote_addr zone=api_ip:20m rate=20r/s;

zone需要为活跃Key状态分配内存。容量取决于:

  • Key长度。
  • IPv4/IPv6。
  • Nginx位数和版本。
  • 状态结构和内存对齐。
  • 同时活跃Key数量。
  • 状态淘汰和过期行为。

$binary_remote_addr通常比文本 $remote_addr 更节省空间。不要把“10MB固定支持多少IP”当永久常量,应按目标版本文档、压测和error log验证。

zone耗尽时,Nginx可能尝试清理旧状态;无法分配新状态时,请求可能被拒绝并记录错误。zone过大也会占共享内存,需按业务基数规划。

十七、Key为空为什么危险

某些zone配置中,Key为空的请求通常不会被计数。例如按自定义Header:

nginx
limit_req_zone $http_x_tenant_id zone=tenant:20m rate=100r/s;

如果客户端不带Header,Key为空,可能绕过该zone;如果Header可伪造,攻击者不断换值也能分散额度。

业务Key必须来自可信认证结果,而不是未经校验的客户端输入。开源Nginx原生是否能得到可信JWT Claim取决于模块和架构,常见做法是由可信API Gateway认证后传递受保护Header,或在业务网关/Redis中做用户级全局限流。

十八、真实IP信任链

错误做法:直接取任意请求的X-Forwarded-For第一个值。

正确示意:

nginx
# 必须替换为真实LB/CDN出口网段
set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

limit_req_zone $binary_remote_addr zone=login_ip:20m rate=5r/s;
mermaid
flowchart TD
    A["客户端可伪造XFF"] --> B["请求是否来自可信代理网段"]
    B -- "否" --> C["忽略伪造链,使用连接来源"]
    B -- "是" --> D["按realip规则还原代理链"]
    D --> E["得到可信remote_addr口径"]
    E --> F["用于限流与日志"]

如果CDN出口网段变化而未更新,真实用户可能全部被识别成代理IP,造成大面积误伤。

十九、按IP限流为什么会误伤NAT用户

同一公司、学校或运营商出口可能有成千上万用户共享一个公网IP。按IP限制登录5r/s可能让正常用户互相争抢同一额度。

分层策略:

  • IP维度:阻止单来源洪峰,阈值相对宽。
  • 账号维度:防止针对单账号爆破,必须在可信认证/业务层实现。
  • 设备维度:辅助风控,不能单独信任客户端设备ID。
  • 全局维度:保护后端总容量。
  • 风险维度:验证码、黑名单和行为分析。

二十、多维Key与组合Key

现代Nginx版本支持使用多个变量组成Key,例如概念上:

nginx
limit_req_zone "$binary_remote_addr:$server_name" zone=ip_host:20m rate=20r/s;

组合Key可避免多个虚拟主机共享同一IP额度,但Key更长、状态更多、zone内存更大。要确认目标版本对多变量Key的支持,并避免可控参数造成Key基数攻击。

二十一、limit_conn限制的不是所有TCP连接

定义状态区:

nginx
limit_conn_zone $binary_remote_addr zone=conn_ip:20m;
limit_conn_zone $server_name zone=conn_server:10m;

应用:

nginx
server {
    limit_conn conn_server 1000;

    location /download/ {
        limit_conn conn_ip 3;
    }
}

HTTP模块通常在请求Header被完整读取、请求进入处理后才对该请求计数,因此它不等于防火墙看到的所有TCP established连接。慢速发送Header的攻击还需要连接层超时、防火墙、WAF和系统参数治理。

二十二、HTTP/2、HTTP/3和limit_conn

HTTP/1.1中连接与并发请求关系相对直观;HTTP/2和HTTP/3可以在一个网络连接上并发多个stream。Nginx文档通常把每个并发HTTP/2/3请求作为limit_conn意义上的独立连接计数,而不是只算底层TCP/QUIC连接一次。

因此:

text
limit_conn 10

不能简单解释成“客户端最多建立10条TCP连接”。必须按协议版本和请求并发验证。

二十三、WebSocket和长下载

WebSocket升级后会长期占用请求/连接状态。长下载也会持续占用连接、带宽和文件FD。此时limit_conn比单纯rate更有价值:

  • rate限制建立新长连接的速度。
  • conn限制同一Key同时保持多少长连接。
  • 带宽还需 limit_rate、CDN或专门传输系统治理。

不能把大文件和普通API使用同一阈值。

二十四、limit_conn状态和dry run

可配置:

nginx
limit_conn_status 429;
limit_conn_log_level warn;
limit_conn_dry_run on;

版本支持要验证。日志可结合 $limit_conn_status 等变量观察命中情况,具体变量值以目标版本为准。

二十五、同一Nginx的多个Worker是否共享

limit_req_zonelimit_conn_zone使用共享内存,同一Nginx master下的Worker通常共享状态:

text
Worker 1收到用户A请求
→ 更新共享zone
Worker 2随后收到用户A请求
→ 读取到同一Key的新状态

这与把计数放在Worker普通进程内存不同。reload期间新旧Worker如何访问共享zone由Nginx管理,但修改zone名称、Key或大小属于配置状态变化,需要金丝雀和验证。

二十六、多Nginx实例为什么不共享额度

mermaid
flowchart TD
    A["上层LB"] --> B["Nginx A:自己的zone"]
    A --> C["Nginx B:自己的zone"]
    A --> D["Nginx C:自己的zone"]

三台Nginx配置每IP 10r/s,并不天然形成全局10r/s。若请求均匀分散,理论总放行可能接近30r/s,且每台还有自己的burst。

这意味着扩容Nginx会改变总限流能力。生产必须明确阈值是:

  • 每实例。
  • 每节点。
  • 每可用区。
  • 还是全局。

二十七、如何实现全局限流

可选方式:

方案优点风险与边界
上层统一API Gateway单点策略和认证上下文清晰网关自身需高可用
Redis/Lua计数多实例共享状态网络延迟、Redis故障、热Key
专用限流服务策略复杂、可统一审计增加关键依赖和调用成本
一致性路由到固定限流节点降低分布式共享需求节点故障和重映射
本地近似限流加全局配额性能高、可分层保护精确性与额度分配复杂

全局强精确限流通常会引入同步共享状态,性能、可用性和一致性需要权衡。保护系统时,近似但可用的本地限流有时比依赖已故障的远程计数器更可靠。

二十八、限流服务故障时放行还是拒绝

这是业务决策:

  • Fail-open:限流依赖故障时放行,保证可用,但后端可能被压垮。
  • Fail-closed:依赖故障时拒绝,保护资源,但正常用户不可用。
  • 分层降级:本地保底阈值继续保护,复杂全局配额降级。

支付、短信、登录、搜索的选择不同。必须在设计阶段写清,而不是故障时临时决定。

二十九、限流和排队的容量关系

排队等待时间近似取决于:

text
前方超额请求数 / rate

例如rate=10r/s,前方有20个延迟请求,尾部请求可能等待约2秒量级。若客户端超时只有1秒,这些请求还没轮到就已断开,排队只会浪费连接和内存。

设计burst时要结合:

  • 客户端超时。
  • Nginx超时。
  • 后端SLA。
  • 可接受排队时延。
  • 连接和内存容量。

三十、登录接口商业策略

目标同时是防爆破和保护密码校验资源。

示意:

nginx
limit_req_zone $binary_remote_addr zone=login_ip:20m rate=5r/s;

location = /api/login {
    limit_req zone=login_ip burst=10 nodelay;
    limit_req_status 429;
    proxy_pass http://auth_backend;
}

还需要业务层:

  • 账号维度失败次数。
  • IP+账号组合。
  • 验证码和风险评分。
  • 密码哈希CPU容量。
  • 避免用户名枚举。
  • 审计和告警。

仅按IP会误伤NAT用户,仅按账号会被攻击者轮换账号。

三十一、短信接口商业策略

短信涉及真实成本,应至少有:

  • IP分钟级速率。
  • 用户分钟/小时/天配额。
  • 手机号配额。
  • 模板配额。
  • 全局供应商额度。
  • 验证码有效期内防重复发送。
  • 幂等请求号。
  • 风控黑名单。

Nginx只能做入口粗限流,手机号和用户配额必须在可信业务层实现,不能从未验证JSON Body直接构造Nginx Key。

三十二、搜索接口商业策略

普通搜索和复杂聚合成本不同:

  • 简单关键词查询阈值较高。
  • 深分页、聚合、导出阈值更低。
  • 匿名用户按IP宽限流。
  • 登录用户按租户/用户配额。
  • 全局限制保护ES线程池。
  • 慢查询、超时和查询复杂度单独治理。

只按请求次数会让一个极重查询和一个轻查询获得同样额度,需要业务层成本权重。

三十三、上传与下载策略

上传需要限制:

  • 新请求速率。
  • 同时上传连接。
  • 请求体大小。
  • 上传超时。
  • 每用户存储配额。
  • 病毒扫描和文件类型。

下载需要:

  • 并发连接。
  • 单连接带宽。
  • CDN分发。
  • 防盗链和签名URL。

limit_req不等于带宽限速,limit_conn也不等于文件配额。

三十四、Webhook与回调策略

支付回调、第三方Webhook可能合法重试。限流过严会拒绝关键通知;完全放开又可能被伪造洪峰攻击。

需要:

  • 来源签名验证。
  • 时间戳和Nonce防重放。
  • IP白名单只能辅助。
  • 快速落库/入队后响应。
  • 幂等事件ID。
  • 合理burst吸收第三方重试波峰。
  • 失败补偿和对账。

三十五、完整分层配置Demo

nginx
http {
    # 示例可信代理网段,生产替换
    set_real_ip_from 10.0.0.0/8;
    real_ip_header X-Forwarded-For;
    real_ip_recursive on;

    limit_req_zone $binary_remote_addr zone=api_ip:20m rate=20r/s;
    limit_req_zone $binary_remote_addr zone=login_ip:20m rate=5r/s;
    limit_conn_zone $binary_remote_addr zone=conn_ip:20m;
    limit_conn_zone $server_name zone=conn_server:10m;

    log_format limited '$request_id $remote_addr "$request" '
                       'status=$status req_limit=$limit_req_status '
                       'conn_limit=$limit_conn_status '
                       'request_time=$request_time';

    server {
        listen 8080;
        access_log /dev/stdout limited;

        limit_conn conn_server 1000;
        limit_req_status 429;
        limit_conn_status 429;

        location = /api/login {
            limit_req zone=login_ip burst=10 nodelay;
            limit_conn conn_ip 3;
            proxy_pass http://auth_backend;
        }

        location /api/search/ {
            limit_req zone=api_ip burst=40 delay=10;
            limit_conn conn_ip 10;
            proxy_pass http://search_backend;
        }

        location /download/ {
            limit_req zone=api_ip burst=10;
            limit_conn conn_ip 3;
            proxy_pass http://file_backend;
        }
    }
}

该Demo只演示Nginx本地策略。生产必须根据可信代理网段、真实容量、多个入口实例和业务配额调整。

三十六、可运行时间线实验

先启动一个监听8081的健康回显后端,再配置:

nginx
limit_req_zone $binary_remote_addr zone=lab:10m rate=2r/s;

server {
    listen 8090;

    location /default-delay {
        limit_req zone=lab burst=3;
        proxy_pass http://127.0.0.1:8081;
    }
}

这里必须使用真正进入content阶段的静态文件或proxy处理器。不要为了省事写 return 200 做限流实验:return属于rewrite模块并可能在preaccess阶段的limit_req处理器执行前就结束请求,导致测试错误地显示“限流不生效”。

记录每次请求开始、HTTP状态和总时间:

bash
for i in 1 2 3 4 5 6; do
  curl -s -o /dev/null \
    -w "request=$i code=%{http_code} time=%{time_total}\n" \
    http://127.0.0.1:8090/default-delay
done

分别替换为:

nginx
limit_req zone=lab burst=3 nodelay;

和:

nginx
limit_req zone=lab burst=3 delay=1;

比较:

  • 哪些请求立即完成。
  • 哪些请求延迟。
  • 哪些请求被拒绝。
  • 突发结束后多久恢复额度。

不同测试请求必须使用同一Key;若经过代理,先确认 $remote_addr没有变化。

三十七、限流未生效排查

  1. nginx -T确认zone与应用位置。
  2. 确认请求命中预期server/location。
  3. 检查Key是否为空。
  4. 检查Key是否每次变化。
  5. 检查真实IP是否被代理错误覆盖。
  6. 检查是否访问不同Nginx实例。
  7. 检查是否处于dry run。
  8. 检查rate、burst和delay是否过宽。
  9. 查看 $limit_req_status 与error log。
  10. 检查配置修改后reload是否成功。

三十八、限流误伤排查

  1. 按Key和URI统计429。
  2. 检查NAT/CDN是否让大量用户共享IP。
  3. 检查客户端是否并发重试放大。
  4. 检查移动端超时后自动重发。
  5. 检查burst排队时间是否超过客户端超时。
  6. 检查多个接口是否错误共用同一zone额度。
  7. 检查发布后实例数变化是否改变总额度。
  8. 检查业务真实峰值与后端容量。

三十九、zone内存异常排查

观察error log中的共享内存分配、状态淘汰和拒绝信息。检查:

  • 活跃Key基数是否突然上涨。
  • Key是否包含随机请求ID导致每请求新状态。
  • 攻击者是否伪造高基数Header。
  • IPv6比例和Key长度。
  • zone大小。
  • 状态是否按预期过期。

不要只无限增大zone;先修复不可控Key和攻击面。

四十、商业场景一:扩Nginx后限流翻倍

原来一台Nginx限制每IP 10r/s,扩成三台后,上层LB随机分发请求,每台都有独立zone,同一IP理论上可获得接近30r/s总额度。

修复方向:

  • 明确阈值是每实例还是全局。
  • 按实例数分配本地额度并留误差。
  • 在统一API Gateway或Redis限流服务做全局额度。
  • 扩缩容流程同步更新限流容量并压测。

四十一、商业场景二:登录限流被XFF绕过

攻击者直接发送不同X-Forwarded-For,旧配置把Header第一个IP作为Key,每次都创建新额度和新zone状态。

修复:只信任明确LB/CDN网段,由realip模块还原地址;非可信来源的XFF仅作为普通不可信输入。结合账号、设备和风险策略,不能只按IP。

四十二、商业场景三:排队请求全部客户端超时

配置rate很低、burst很大且默认delay。大量请求留在Nginx等待数秒,但移动端1秒超时后主动断开并重试,造成:

  • 排队连接和内存增加。
  • 客户端重试流量增加。
  • 用户仍然失败。
  • 后端随后收到过期请求。

修复:按SLA限制最大排队时延,必要时快速429让客户端指数退避;对必须可靠处理的任务应入MQ,而不是在Nginx连接上长时间排队。

四十三、商业场景四:短信接口只按IP被薅

攻击者使用代理IP池轮换来源,每个IP都获得新额度。Nginx IP限流看起来正常,但手机号和用户总发送量失控。

治理:业务层按用户、手机号、设备、模板和全局供应商额度控制,使用幂等请求号、验证码有效期防重复、风险评分和成本告警;IP限流只作为第一层。

四十四、面试标准回答

rate、burst和nodelay分别是什么

rate定义每个Key的长期平均请求速率;burst定义可容纳的超额请求数量,默认超额请求按rate延迟,超过burst拒绝;nodelay让burst范围内的超额请求立即放行,但仍记录速率债务,后续突发空间要随时间恢复,不是永久增加额度。

burst是不是每秒额外放行数量

不是。它是某个Key当前可容纳的超额状态上限,与请求到达时间和债务消化有关,不按自然秒重置。默认可形成延迟队列,nodelay时立即放行但占满突发空间;超过burst仍拒绝。

limit_req多个Worker是否共享

同一Nginx实例的Worker通过limit_req_zone共享内存共享Key状态,因此不会每Worker各给一份额度;但不同主机或Pod的zone不共享,三个实例各10r/s可能形成约30r/s总额度,全球限流要用统一网关或共享状态服务。

limit_conn限制的是TCP连接数吗

不完全是。HTTP模块通常在请求Header读完并进入处理后计数,不包含所有尚未形成请求的TCP连接;HTTP/2/3中并发stream可能分别计数。慢Header攻击、SYN洪峰和系统连接上限还要在超时、防火墙和内核层治理。

为什么不能直接按X-Forwarded-For限流

它是客户端可伪造的HTTP Header。只有请求来自明确可信LB/CDN网段时,才能由realip模块按代理链还原客户端地址;否则攻击者轮换伪造值即可绕过额度并制造高基数Key。

Nginx限流和Redis全局限流怎么选

Nginx本地zone性能高、故障依赖少,适合入口保底,但多实例不共享;Redis/Lua可做用户或租户全局额度,但增加网络延迟、热Key和Redis故障处理。商业系统常用Nginx本地粗限流保护节点,再由网关/Redis做可信用户全局配额。

四十五、学习验收

  1. 画出Key查找、时间衰减、超额计算、延迟和拒绝过程。
  2. 对rate=2r/s、burst=3逐请求推导时间线。
  3. 比较默认delay、nodelay和delay=1。
  4. 解释nodelay放行后为什么还要消化债务。
  5. 比较漏桶与令牌桶的突发行为,不说完全等价。
  6. 根据Key基数和长度设计zone并观察内存异常。
  7. 证明伪造XFF不能绕过可信真实IP限流。
  8. 解释NAT误伤并设计IP、账号和全局多层策略。
  9. 验证limit_conn在HTTP/1.1与HTTP/2并发请求下的差异。
  10. 扩容三个Nginx实例,解释本地额度如何放大。
  11. 为登录、短信、搜索、上传和Webhook分别设计策略。
  12. 使用dry run和状态变量完成上线前评估。
  13. 定位一次限流未生效、一次误伤和一次zone高基数问题。
  14. 写出全局限流服务故障时fail-open、fail-closed和本地降级方案。

关联知识点