openresty 通过 stream-lua-nginx-module 和 lua-resty-limit-traffic 实现四层连接频次限速,本质是将 tcp 新连接建立视为“请求”,以令牌桶控制每秒新建连接数,非 nginx 官方指令。

OpenResty 或 Nginx 本身不支持对 TCP/UDP(即四层)流量直接使用 limit_req(该指令仅作用于 HTTP 七层请求),所以严格来说,stream_limit_req 并不是 Nginx 官方模块的内置指令——它并不存在于标准 Nginx 或官方 stream 模块中。
但你提到的 “stream_limit_req(配合插件)”,大概率是指通过第三方模块(如 openresty/stream-lua-nginx-module)结合 Lua 脚本,在 stream 上下文中模拟限速逻辑;或更常见的是,使用 openresty 的 lua-resty-limit-traffic 库 + stream-lua 模块实现四层连接级或数据包级的速率控制。
确认前提:你用的是 OpenResty(非原生 Nginx)
原生 Nginx 的 stream { } 块只支持 limit_conn(连接数限制),不支持按请求速率(如 r/s)限流。要实现“每秒请求数”语义的四层限速,必须借助:
- OpenResty 发行版(含
stream-lua-nginx-module) - Lua 脚本 +
lua-resty-limit-traffic(推荐 v0.08+) - 合适的限速维度(如客户端 IP、目的端口、或连接元信息)
核心思路:在 stream 阶段用 Lua 实现“连接建立频次”限制
四层没有“请求”概念,但可将“新 TCP 连接建立”(即 accept)视为一次“请求”。限速目标通常是:防止某 IP 在单位时间内发起过多新连接(防扫描、CC 类攻击)。操作步骤如下:
- 在
stream { }中启用lua_socket_connect_timeout等基础配置 - 用
preread_by_lua_block或init_worker_by_lua_block初始化限速对象(推荐后者,避免重复创建) - 在
access_by_lua_block中对 client_addr 做令牌桶/漏桶判断 - 若超限,调用
ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)或直接ngx.close()
示例配置片段(限单 IP 每秒最多 5 个新连接):
stream {
# 加载 Lua 模块
lua_package_path "/path/to/lua-resty-limit-traffic/lib/?.lua;;";
<pre class="brush:php;toolbar:false;">init_worker_by_lua_block {
local limit_conn = require "resty.limit.conn"
-- 注意:这里用 conn 模块限“连接频次”,非 traffic(字节)或 req(HTTP)
local lim, err = limit_conn.new("conn_store", 5, 1) -- 5 conn/s
if not lim then
ngx.log(ngx.ERR, "failed to instantiate a resty.limit.conn object: ", err)
return
end
ngx.ctx.limit_conn = lim
}
upstream backend {
server 127.0.0.1:8080;
}
server {
listen 8000;
proxy_pass backend;
access_by_lua_block {
local lim = ngx.ctx.limit_conn
local key = ngx.var.remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
if err == "rejected" then
ngx.log(ngx.WARN, "connection rejected for ", key)
ngx.exit(503)
end
ngx.log(ngx.ERR, "failed to limit: ", err)
return
end
}
}}
注意事项与常见误区
-
不是所有“四层请求”都能被等同为连接:UDP 是无连接的,需用
balancer_by_lua_block+ 报文解析(如识别 DNS 查询 ID)做会话级频控,复杂度高,通常改用limit_conn控制并发 UDP socket 数 - 限速粒度是“连接建立”而非“数据包”:无法限制单连接内每秒发多少包(那属于 TCP 流控或内核限速范畴,如 tc / netem)
-
共享内存区(shm)必须预定义:在 http 或全局块中加
lua_shared_dict conn_store 10m;,否则限速状态无法跨 worker 共享 - 不要在 preread_by_lua_block 中做耗时操作:DNS 解析、外部调用等会阻塞 accept,应移至 access 或 balancer 阶段
替代方案:更轻量、更稳定的原生方式
如果只需基础防护,优先考虑:
-
stream { limit_conn addr 100; }+limit_conn_zone $binary_remote_addr zone=addr:10m;(限制并发连接数) - 系统级工具:
iptables -A INPUT -p tcp --dport 8000 -m connlimit --connlimit-above 5 --connlimit-mask 32 -j REJECT - 专用四层网关:如 Traefik TCP routers + rate-limiting middleware(需 v2.9+)、或自研基于 eBPF 的限速器(如 Cilium)
本质上,所谓“stream_limit_req”是社区对 Lua 方案的一种通俗叫法,并非真实指令。真正落地时,关键是选对限速模型(连接频次?并发数?字节速率?)、匹配协议特性(TCP/UDP)、并确保状态共享与性能开销可控。











