单靠一个limit_req_zone难以支撑千万级独立IP限流,主因是共享内存开销大(约64字节/IP),1000万IP需约640MB;应改用聚合key、分层zone和外部兜底协同优化。

单靠一个 limit_req_zone 区域很难在常规内存下支撑千万级独立 IP 的限流,核心瓶颈不在算法,而在共享内存区的存储开销。但通过合理设计 key、分层策略和内存复用,可以逼近这个目标。
单个 IP 状态的内存开销很实在
每个客户端 IP 在 limit_req_zone 中至少占用约 64 字节(含哈希桶头、计数器、时间戳、锁等元数据)。官方文档虽未明说,但实测与社区共识一致:1MB 内存 ≈ 存储 1.6 万个独立 IP 状态。这意味着:
- 100 万 IP → 至少需要约 64 MB 共享内存
- 1000 万 IP → 至少需要约 640 MB,接近 1 GB
- 若使用
$remote_addr(文本形式),IPv4 地址平均占 12 字节以上,整体内存还会再增 15%~20%
关键优化:换 key,不硬扛 IP
直接按 $binary_remote_addr 存千万 IP 是最笨的办法。更实用的路径是降低 key 的维度或引入聚合:
- 用
$geoip2_country_code或$http_x_forwarded_for的前两段(如192.168)做粗粒度限流,单个 key 覆盖成百上千 IP - 结合
map指令动态构造 key:对登录用户用$http_x_user_id,对未登录用户回落到 IP 前缀,避免全量记 IP - 对 CDN 回源流量,用
$http_x_real_ip+$server_name组合,把“同一源站 + 同一客户”视为一个逻辑单元,大幅压缩 key 总数
拆分 zone + 分级限流,让内存各司其职
不把所有压力堆在一个 zone 上。例如:
-
zone=burst_api:5m rate=100r/s:专用于突发接口,只存活跃 IP(自动淘汰 60 秒无访问者) -
zone=login_basic:2m rate=3r/m:登录页限流,key 用$binary_remote_addr,但速率极低,长期存活 IP 少 -
zone=user_action:8m rate=20r/s:key 为$http_x_user_id,覆盖真实用户,数量远小于 IP 总量
这样总内存仍可控(如 15 MB),却能覆盖行为、身份、来源等多个层面,实际支撑能力远超单一千万 IP zone。
必须配合的兜底手段
即使做了上述优化,纯 Nginx 内存方案仍有硬上限。生产环境建议补充:
- 在 LVS 或云 WAF 层做第一道 IP 频控(基于连接数或包速率),过滤掉明显攻击流量
- 对高频异常 IP,由后端服务写入 Redis 黑名单,Nginx 通过
lua-resty-redis实时查表跳过限流逻辑 - 开启
limit_req_log_level warn和limit_req_status 429,配合日志分析真实热点 key,持续反哺 zone 策略调优











