10MB内存最多存储约16万个IP的状态,因每个IP在limit_req_zone中固定占用约64字节;百万级IP需至少64MB,无法通过调整rate或burst缓解,须改用维度压缩、行为分层等替代方案。

做不到。10MB 内存撑不起百万级独立 IP 的 limit_req_zone 限流,这不是配置写得够不够巧的问题,而是每个 IP 状态在共享内存里硬占约 64 字节的物理事实。
为什么 10MB 只能存约 16 万个 IP?
Nginx 为每个活跃 IP 在 limit_req_zone 共享内存区中分配一个固定结构的状态槽,含时间戳、计数器、哈希元数据和锁,实测平均约 64 字节。不考虑对齐和碎片,粗算:
- 1 MB ≈ 16,000 个 IP 状态
- 10 MB ≈ 160,000 个 IP 状态
- 100 万 IP → 至少需要约 64 MB,不是 10 MB
用 $binary_remote_addr 能省掉字符串解析开销,但无法绕过这个底层存储模型。如果硬把 key 设成 $remote_addr(点分十进制字符串),IPv4 地址平均占 12+ 字节,整体内存还会再涨 15%~20%,更吃紧。
常见错误:以为增大 burst 或调 rate 就能“撑住更多 IP”
burst 和 rate 控制的是请求节奏,不影响每个 IP 占多少内存。它们只决定“这个 IP 的请求怎么排队/丢弃”,不改变“要不要给这个 IP 分配状态槽”。所以:
- 把
rate=1r/s改成rate=0.1r/s,不会让内存多存一个 IP - 加
burst=100,只是让队列变长,状态槽该占 64 字节还是占 64 字节 - zone 满了之后,Nginx 会淘汰 60 秒内无访问的旧记录;若淘汰后仍不足,新 IP 的状态写不进去,
limit_req对它完全失效——你看到的现象是“某些 IP 根本不被限”
真正能在 10MB 内覆盖百万级流量的替代思路
放弃“记住每个 IP”的执念,转向维度压缩与行为分层:
- 用
map动态构造 key:对已登录用户用$http_x_user_id,未登录用户回落到$binary_remote_addr前两段(如192.168),把上千个 IP 映射为一个 key - 对 CDN 回源流量,key 改为
$http_x_real_ip$http_host组合,把“同一源站 + 同一业务域名”视为一个逻辑单元 - 敏感路径单独建 zone:比如
zone=login:2m rate=3r/m专守登录页,key 用$binary_remote_addr$http_user_agent防 UA 轮换,2MB 就能覆盖高风险行为,不浪费在静态资源上 - 白名单彻底跳过:用
map把监控探针、内部服务、CDN 回源 IP 映射为空字符串,这些请求连状态槽都不进,零内存消耗
10MB 不是用来装百万 IP 的,是用来装“百万流量里最关键的几千个行为单元”的。漏掉的 IP 不是漏洞,而是设计取舍——真正的防护靠的是 LVS 层包速率初筛 + Redis 黑名单兜底,Nginx 的 limit_req_zone 只负责那层最轻、最快、最贴近请求路径的控制。











