nginx共享内存限流需在http块定义唯一命名的共享字典(如lua_shared_dict rate_limit 10m),用原子操作incr避免竞态,key宜选短且确定的字段(如$binary_remote_addr),简单限流用limit_req,复杂逻辑用lua_shared_dict。

用 Nginx 共享内存做限流,核心是让所有 worker 进程共用一块内存区域来计数,避免锁、网络或序列化开销,单次操作可压到 100 纳秒级。关键不在“能不能用”,而在“怎么定义、怎么读写、怎么选 key”。
必须在 http 块定义共享字典
lua_shared_dict 或 limit_req_zone 都只能出现在 http {} 块里。写在 server 或 location 里会直接启动失败,报错类似 [emerg] "lua_shared_dict" directive is not allowed here。名称要唯一,大小单位必须是 k 或 m(比如 10m,不能写 10MB 或 10M),名称只支持字母、数字、下划线,且大小写敏感。
- 正确示例:
http { lua_shared_dict rate_limit 10m; } - 错误写法:多个 include 文件里重复定义同名字典,或混用
RateLimit和ratelimit
原子操作是刚需,别手写 get + set
多 worker 场景下,get → 判断 → set 是典型竞态漏洞。两个请求同时读到 val == 99,都会执行 set(key, 100),结果漏放一个请求。必须用原子指令:
- Lua 场景:用
dict:incr(key, 1, expire),第三个参数是过期时间(秒),不填则永不过期 - 原生 limit_req:靠模块内部保证原子性,无需手动干预
key 设计决定内存用量和准确性
内存不是无限的。10MB 共享内存最多存约 16 万个 IPv4 地址的状态(每个约 64 字节),但实际容量受 key 长度影响极大:
- 最省内存:
$binary_remote_addr(IPv4 固定 4 字节,IPv6 为 16 字节) - 但 CDN 后真实 IP 会被掩盖,此时
$binary_remote_addr变成 CDN 节点 IP,所有用户被聚合成几个“超级 IP” - 更准但需权衡:
$http_x_real_ip(需配合set_real_ip_from)、短 token(如$arg_token或$http_x_client_id,建议 ≤32 字节) - 绝对避免:
$http_x_forwarded_for(长度不可控,易 OOM)或带参 URI(动辄上百字节)
按场景选方案:简单计数用 limit_req,复杂逻辑用 lua_shared_dict
两者都走共享内存,但定位不同:
-
limit_req:适合标准漏桶/令牌桶限流(如每秒 10 次、突发 5 个),配置简单,内置算法可靠,但窗口固定、无法自定义滑动逻辑 -
lua_shared_dict:适合需要自定义规则的场景,比如按 URI 分级限流、IP+Token 复合 key、滑动窗口(自己维护时间戳)、令牌桶重置逻辑等 - 注意:
lua_shared_dict数据不持久(reload 清空)、不跨机、value 不支持函数或循环 table











