生产环境首选 nginx 内置 limit_conn 限制 IP 并发连接数,它开箱即用、无依赖、不阻塞 worker;需用 $binary_remote_addr 定义 zone,不可与限请求频率的 limit_req 混用。

nginx 里用 limit_conn 做 IP 连接数限制最稳
直接上结论:生产环境首选 limit_conn,不是靠写 Lua 或改内核参数硬扛。它由 nginx 内置模块 ngx_http_limit_conn_module 实现,开箱即用、无额外依赖、不阻塞 worker 进程。
常见错误是把 limit_conn 和 limit_req 混用——后者限的是请求频率(QPS),前者才真正限制并发 TCP 连接数。比如一个长轮询接口被单个 IP 建了 200 个连接,limit_req 完全不管,只有 limit_conn 能拦住。
-
limit_conn_zone $binary_remote_addr zone=perip:10m:必须用$binary_remote_addr(不是$remote_addr),节省内存且支持 IPv6;10m足够存约 16 万个 IP 计数器 -
limit_conn perip 10:放在server或location块里,表示每个 IP 最多 10 个并发连接 - 注意作用域:
limit_conn不支持在if中使用,也不能嵌套生效;如果想对某些路径放行,得用limit_conn off显式关闭
为什么不用 openresty 的 lua-resty-limit-traffic
它灵活,但真没必要为单纯限连接数引入 Lua。实际踩过的坑包括:
- 计数器默认存在 shared dict,但 dict 大小没配够时,新连接会静默 fallback 到不计数(现象是限流完全失效)
- 在 keepalive 场景下,同一个 socket 复用多次请求,Lua 计数器可能重复累加或漏减,需要手动管理 connection 生命周期
- worker 间共享状态依赖
lua_shared_dict,而它的内存分配是预分配的,扩容需 reload,不如 nginx 原生命令直观
除非你要做动态阈值(比如根据上游负载实时调连接上限),否则纯 Lua 方案只是把简单问题复杂化。
iptables 只能当临时补丁,别当主力
用 iptables -A INPUT -p tcp --dport 80 -m connlimit --connlimit-above 10 --connlimit-mask 32 -j REJECT 确实能拦,但它工作在网络层,没法区分 HTTP 层的虚拟主机或 path,更没法和 nginx 日志联动。
- 一旦触发,连接直接被 kernel 丢弃,客户端收不到 503,只看到 Connection refused 或 timeout,排查困难
-
--connlimit-mask 32对 IPv4 是单 IP,但 IPv6 下 /64 才是合理粒度,硬写 128 会崩掉整个模块 - 规则生效后无法按域名、location 或 upstream 分组限流,灵活性归零
自定义计数器真要写,就用 redis + incrby + expire
如果业务逻辑特殊(比如“同一账号下所有设备 IP 总连接数 ≤ 5”),nginx 原生搞不定,就得自己记。这时候 redis 是唯一靠谱选择——memcached 不支持原子 incr + expire 合并操作,本地文件或内存变量跨 worker 不一致。
- key 设计用
conn:ip:{ip}或conn:uid:{uid},避免 key 冲突 - 务必用
INCRBY key 1+EXPIRE key 60组合,不能先 GET 再 SET,否则并发时计数错位 - 超限时返回
503 Service Temporarily Unavailable,别用 429,因为 429 是给 rate limit 用的,语义不符
真正难的不是写这几行代码,而是 redis 故障时的降级策略——比如是否允许缓存击穿后放过连接,还是宁可拒绝也不让服务雪崩。这个决策点,文档里从来不会写。










