不能直接用 $remote_addr 做 reqstat key,因其导致 key 过于分散、内存耗尽、无法回收且不支持复合统计;推荐用 lua_shared_dict + log_by_lua_block 实时计数,或降维为 C 段 IP 归一化处理。

ngx_http_reqstat_module 本身不直接支持按 IP 统计异常请求频次,它主要面向 $server_name、$upstream_addr、$status 等预定义变量做聚合,且 key 必须是字符串型、长度可控、离散度适中。而 $remote_addr 直接用作 reqstat key 会导致 key 过于分散(如百万级 IP),迅速耗尽共享内存,触发统计失效或 Nginx 报错。
为什么不能直接用 $remote_addr 做 reqstat key
reqstat 的 zone 共享内存按 key 哈希存储,每个 key 占用固定开销(约 128–256 字节)。若将未聚合的原始 IP 作为 key:
- 10 万个活跃 IP 就需至少 12.5 MB 内存,仅用于 key 存储,不包含指标数据;
- IP 动态变化频繁,key 不会自动回收,长期运行后内存碎片升高、命中率下降;
- 模块无内置“按状态码过滤 + 按 IP 分组”的复合统计能力,无法直接产出“某 IP 的 499 请求次数”这类结果。
推荐替代方案:用 lua_shared_dict + log_by_lua_block 实时计数
这是生产环境最稳定、低开销、可精准控制的方式,特别适合监控 4xx/5xx/499 等异常响应的来源 IP:
- 在 http 块定义共享字典:lua_shared_dict ip_abnormal 100m;
- 在 server 或 location 块内添加日志阶段逻辑:
log_by_lua_block {
local status = tonumber(ngx.var.status)
local ip = ngx.var.binary_remote_addr -- 使用 binary 形式更省内存
local dict = ngx.shared.ip_abnormal
<pre class="brush:php;toolbar:false;">if status == 499 or status >= 400 then
dict:incr(ip, 1)
end
}
- 该逻辑在每次响应完成后执行,不影响主请求流程;
- binary_remote_addr 比 remote_addr 节省内存约 30%,且避免 IPv4/IPv6 格式混杂;
- 可通过 dict:get(ip) 在其他 location(如 /api/ip-stats)中读取并返回 JSON 数据。
补充增强:结合 limit_req 实现自动拦截
当某个 IP 异常请求频次超过阈值(如 5 分钟内 50 次 499),可联动限流模块实时阻断:
- 先定义一个基于 IP 的限速区:limit_req_zone $binary_remote_addr zone=abn_limit:10m rate=10r/m;
- 在 log_by_lua_block 中检测并触发限速(需配合 lua-resty-limit-traffic 或自定义标记);
- 更轻量的做法:用 dict 记录“是否已标记为恶意”,在 access_by_lua_block 中提前拒绝:
access_by_lua_block {
local dict = ngx.shared.ip_abnormal
local count = dict:get(ngx.var.binary_remote_addr) or 0
if count > 50 then
return ngx.exit(429)
end
}
若仍想用 reqstat,可降维折中处理
适用于只需宏观识别“高异常 IP 段”而非精确到单 IP 的场景:
- 用 map 指令将 IP 归一化为 C 段:map $remote_addr $ip_cblock { ~(?
\d+\.\d+\.\d+)\. $ip.0; default "0.0.0.0"; } - 再配置:reqstat $ip_cblock$status; —— 得到类似 “192.168.1.0-499” 的组合 key;
- 通过 /reqstat 接口查询时,用正则匹配 status 后缀,筛选出异常比例高的网段。










