openresty+redis是实现nginx分钟级top ip实时统计的最优方案,因其可在access阶段动态获取真实ip、构造带时间戳的redis key(如ip_count:202605091905:192.168.1.100)、原子增减并设ttl,毫秒级完成且不阻塞主流程。

要实现 Nginx 层面的分钟级 Top IP 实时统计,不能依赖离线日志分析(如 awk + sort),而需在请求处理过程中实时计数、聚合、过期清理——OpenResty + Redis 是目前最轻量、最可靠、生产就绪的方案。
为什么必须用 OpenResty 而非原生 Nginx
原生 Nginx 缺乏运行时执行逻辑的能力:limit_conn_zone 只能做连接限制,log_format 仅支持静态变量,无法动态读写计数器。OpenResty 内置 LuaJIT 和 resty.redis 模块,允许你在 access 阶段精准获取真实 IP、按分钟维度构造 Redis key、原子增减计数,并自动设置 TTL,整个过程毫秒级完成,不阻塞主流程。
分钟级 IP 计数的核心实现
关键在于把“当前分钟”作为时间戳嵌入 Redis key,例如 ip_count:202605091905:192.168.1.100(格式为 ip_count:YYYYMMDDHHMM:IP)。这样每分钟自然分片,无需定时清理旧数据,靠 Redis 自动过期即可。
- 在
access_by_lua_block中获取真实客户端 IP:优先读ngx.var.http_x_real_ip或ngx.var.http_x_forwarded_for, fallback 到ngx.var.remote_addr - 生成分钟级 key:
local now = os.date("%Y%m%d%H%M"); local key = "ip_count:" .. now .. ":" .. ip - 调用 Redis INCR 并设 TTL:
red:incr(key); red:expire(key, 60)(注意 expire 必须紧跟 incr,避免 key 被提前删除) - 若需防穿透或限流,可在此处加判断:比如
if tonumber(res) > 100 then ngx.exit(429) end
查询当前分钟 Top IP 的方法
Redis 本身不支持跨 key 排序,所以不能直接用 SMEMBERS + SORT。推荐两种实用方式:
-
方式一:用 SCAN 批量拉取 + Lua 脚本聚合(适合中小流量)
写一个 Lua 脚本,SCAN 匹配ip_count:202605091905:*,对每个 key 执行 GET,存入临时 table 后按值排序,返回前 10。通过redis-cli --eval或管理接口调用 -
方式二:用 RedisTimeSeries(RTS)模块(高吞吐场景)
安装 Redis 7+ 并启用 RTS,为每个 IP 创建时间序列,用TS.RANGE查最近 60 秒数据,再用TS.MRANGE+AGGREGATION SUM汇总各 IP 总量。比字符串 key 更节省内存,支持毫秒级滑动窗口
配套运维与可观测性建议
光有计数不够,还需留痕、告警、导出:
- 封禁或限流动作发生时,用
ngx.log(ngx.WARN, "TopIP rate limit triggered for ", ip, " at ", now)记入 error.log,便于关联审计 - 暴露一个内部 HTTP 接口(如
/api/topip?minutes=1&limit=10),后端用 Lua 调用 Redis 并 JSON 返回,供 Grafana 或值班群机器人调用 - 每小时用 cron 触发脚本,将 Redis 中上一分钟的 Top IP 数据
GET出来,写入 SLS 或本地 CSV,用于长期趋势分析











