必须用openresty而非原生nginx,因其内置luajit和redis客户端,支持在access_by_lua_file阶段原子执行redis限流逻辑;原生nginx无lua解释器和redis直连能力,第三方模块功能弱且维护难。

直接在 Nginx 层面做分布式限流,核心是绕过单机内存限制,把令牌桶或计数器状态交给 Redis 统一管理,再用 Lua 脚本保证原子性操作。OpenResty 是最常用、最稳妥的选择,它已内置 Lua 和 redis 客户端支持,无需额外编译。
为什么必须用 OpenResty 而不是原生 Nginx
原生 Nginx 不支持直接调用 Redis,也没有内嵌 Lua 解释器。OpenResty 把 Nginx、LuaJIT 和 lua-resty-redis 等模块打包集成,让你能在 access_by_lua_file 或 content_by_lua_file 阶段安全执行 Redis 读写+逻辑判断。如果硬要用原生 Nginx,只能靠第三方模块(如 HttpRedis2Module),但功能受限、维护成本高,不推荐。
关键配置:Nginx + Lua 脚本联动
在 server 或 location 块中启用 Lua 执行点,例如:
- 用 access_by_lua_file 在请求进入代理前拦截并决策,适合限流场景
- 避免用 content_by_lua_*,它发生在响应生成阶段,此时请求已处理,限流失去意义
- worker_processes 建议设为 auto 或具体数值,确保每个 worker 可独立连接 Redis(连接池需合理配置)
限流逻辑必须由 Lua+Redis 共同完成
不能只靠 Nginx 的 limit_req_zone(那是单机漏桶,不跨实例)。典型做法是:
- 定义唯一限流 key,比如 "rate:ip:"..ngx.var.binary_remote_addr 或 "rate:api:"..ngx.var.uri
- 在 Lua 脚本里调用 Redis 的 EVAL 执行预写好的 Lua 脚本(非简单 GET/INCR),实现“读取旧状态→计算新令牌→判断是否放行→更新状态”四步原子操作
- 令牌桶参数(如 tokens_per_second、max_tokens)可硬编码,也可从 Redis Hash 中动态读取,便于运行时调整
Redis 脚本要兼顾精度与性能
推荐使用带时间戳校准的令牌桶脚本,而不是简单计数器。例如:
- 每次请求传入当前时间 ngx.now(),Redis 脚本根据上次访问时间差补发令牌,避免时间漂移累积误差
- 用 HMGET/HMSET 存 last_access_time 和 tokens 字段,比用单独 KEY 更节省内存和网络往返
- 脚本返回 1 表示放行,0 表示拒绝,Nginx 根据返回值调用 ngx.exit(429) 或继续 proxy_pass











