生产中不可行直接为千万IP配置limit_req_zone——需640MB内存且无法压缩;应通过聚合降维、分层复用将状态压至数百逻辑单元,按真实活跃key数精准分配内存。

直接按千万个独立 IP 配置 limit_req_zone 共享内存,在生产中不可行——每个活跃 IP 状态实际占用约 64 字节,1000 万 IP 就需要近 640MB 内存,且状态无法压缩、淘汰被动、超出即失效。所谓“支持千万级静态访问限流”,关键不是硬存全部 IP,而是通过精准建模 + 聚合降维 + 分层复用,把有效限流状态压到几千甚至几百个逻辑单元。
按真实活跃 key 数反推内存,不拍脑袋填 10m 或 100m
先确认你真正要限的“活跃维度”是什么,再统计其峰值数量:
- 若核心用户已登录,用
$http_x_user_id为 key,日志中查出峰值活跃用户数(比如 80 万),则最小内存 = ⌈800000 ÷ 16000⌉ × 1.2 ≈ 60MB - 若面向海外未登录流量,用
$geoip2_country_code(全球国家数 < 250),10MB 就绰绰有余 - 若 CDN 回源请求集中,用
$http_x_real_ip+$server_name组合,一个源站对一个客户视为一个 key,总量通常在数百至数千级 - 切忌用
zone=perip:100m这类大而全配置——它既浪费内存,又因锁竞争加剧抖动,还掩盖了真实风险点
用聚合 key 替代单 IP,把千万压成百量级
放弃“每个 IP 一个槽位”的思路,改用稳定、可归类的业务标识:
- 对国内流量,用
map提取$remote_addr前两段(如112.65),代表骨干网出口,一个 key 覆盖上万个终端 - 对移动端 API,用设备指纹哈希前缀(如
md5($http_user_agent . $http_x_device_id)截取前 8 字符),冲突可控、长度固定 - 对爬虫或扫描流量,提取高频 UA 特征(如
AhrefsBot、DotBot)单独映射为 key,不记 IP 只记行为类型 - 避免使用
$request_uri或完整$http_x_forwarded_for,它们长度不可控,极易触发 OOM
分层 zone 设计,让每块内存干好一件事
多个小 zone 比一个大 zone 更稳定、更易扩容、故障隔离性更好:
-
zone=api_strict:5m rate=2r/s:只作用于/login或/pay,key 用$binary_remote_addr$http_user_agent,防轮换爆破 -
zone=static_light:30m rate=100r/s:用于/static/或/images/,key 用$server_name,不设burst,省空间 -
zone=global_flood:2m rate=200r/s:兜底防护,key 是$server_name,防整个域名被刷垮 - 所有 zone 总内存可控(例如合计 40MB),却覆盖了身份、路径、全局三层,比单个 500MB zone 更可靠
白名单跳过低价值流量,彻底释放内存压力
不是所有请求都值得分配状态槽。用 map 把确定可信的来源映射为空 key:
- CN2/骨干网回源段、CDN 节点地址(如 Cloudflare / 阿里云全站加速)、K8s Service CIDR 全部归入空 key
- 健康检查路径(如
/healthz)、内部服务调用头($http_x_internal_call = "true")也跳过限流 - 示例:map $remote_addr $limit_key { ~^10\. ""; ~^172\.1[6-9] ""; default $binary_remote_addr; }
- 这样,90% 的低风险流量完全不进共享内存区,有限资源只留给真正需要控制的部分

















