OpenResty通过Redis实现分布式限流,支持固定窗口(incrby)、滑动窗口(zset+Lua脚本)及lua-resty-limit-traffic模块三种方式,兼顾精度、性能与可靠性,并需考虑降级、key设计和可观测性。

OpenResty 通过 Lua 脚本结合 Redis(或其他共享存储)可实现分布式计数器限流,核心思路是:将计数状态从单机内存提升到全局共享存储,利用原子操作保证并发安全。
使用 Redis + incrby 实现固定窗口计数器
这是最简单直接的分布式限流方式,适合对精度要求不高、能接受窗口切换时突增流量的场景。
- 在
access_by_lua*阶段读取并更新 Redis 中的计数器,例如按用户 ID 或 IP 哈希分组 - 使用
redis:incrby(key, 1)原子递增,并用redis:expire(key, window_sec)设置过期时间(注意:需确保 key 首次设置时同时设 TTL,避免竞态) - 若返回值超过阈值,调用
ngx.exit(429)拒绝请求
示例片段:
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
red:connect("127.0.0.1", 6379)
local key = "rate_limit:" .. ngx.var.remote_addr
local count, err = red:incrby(key, 1)
if not count then
ngx.log(ngx.ERR, "redis incr error: ", err)
return
end
if count == 1 then
red:expire(key, 60) -- 第一次写入才设过期,防重复设置
end
if count > 100 then
ngx.status = 429
ngx.say("Too many requests")
ngx.exit(429)
end用 Redis Lua 脚本实现滑动窗口(更精确)
固定窗口有临界问题,滑动窗口能更好平滑限流。OpenResty 可调用 Redis 的 EVAL 执行内嵌 Lua 脚本,保证“读-判-写”原子性。
- 使用 Redis 的 sorted set(zset)存储时间戳,score 为毫秒时间,member 为唯一请求标识(如 request_id 或空字符串)
- 脚本中先
ZREMRANGEBYSCORE清理过期项,再ZCARD获取当前窗口请求数,最后ZADD新条目 - 整个逻辑封装为一个 Lua 脚本,由 OpenResty 用
red:eval(script, 1, key, now, window_ms, max_count)调用
优点是无竞态、精度高;缺点是 zset 内存开销略大,高频小窗口下需评估 Redis 性能。
借助 lua-resty-limit-traffic 做声明式限流
OpenResty 官方模块 lua-resty-limit-traffic 已内置基于 Redis 的分布式限流支持(v0.08+),推荐优先使用,减少手写逻辑错误。
- 配置 Redis 连接池,初始化 limit 对象时指定
redis_host、redis_port等参数 - 选择策略:如
limit.count(计数器)、limit.conn(连接数)、或limit.req(漏桶/令牌桶) - 调用
limiter:incoming("my_key", true),返回剩余配额与是否受限;支持自定义 key(如"user:" .. ngx.var.arg_uid)
它内部已处理连接复用、失败降级(可 fallback 到本地内存计数)、以及原子脚本调用,生产环境更可靠。
注意事项与优化建议
分布式限流不是“加个 Redis 就行”,还需关注稳定性与可观测性。
- Redis 故障时应有降级策略,比如 fallback 到
lua_shared_dict做单机限流(用zone配置共享字典),避免雪崩 - 限流 key 设计要合理:避免全站共用一个 key;推荐按业务维度拆分,如
"api:/order/create:uid_123" - 开启
lua_code_cache off仅用于开发调试,生产务必开启以避免重复加载 Lua 脚本影响性能 - 配合 OpenResty 日志变量(如
$limit_rate_status)记录限流行为,便于后续分析和告警


















