openresty秒杀限流核心是shared dict原子计数与lua-resty-limit-traffic精细化控制,推荐令牌桶/漏桶方案;需在http块定义lua_shared_dict,location中用access_by_lua_block调用limit:incoming实现毫秒级限流,并支持按用户/ip等动态key;库存扣减须用shared dict的incr保证原子性防超卖。

OpenResty 通过 Lua 脚本实现秒杀限流,核心是利用 共享内存字典(shared dict) 做原子计数 + lua-resty-limit-traffic 库做精细化控制,避免单点瓶颈和竞态问题。关键不在“写 Lua”,而在“怎么用 OpenResty 的并发模型和共享状态做安全限流”。
用 lua-resty-limit-traffic 实现令牌桶/漏桶限流
这是最推荐的方案——它基于 shared dict,所有 worker 进程共用同一份计数器,支持毫秒级精度和原子增减,天然适合高并发秒杀场景。
- 在
nginx.conf的http块中预定义共享字典:
- 在 location 中加载并使用限流模块(以每秒最多 1000 次请求为例):
access_by_lua_block {
local limit = require "resty.limit.count".new("seckill_limit", 1000, 1)
local key = "global"
local delay, err = limit:incoming(key, true)
if not delay then
if err == "rejected" then
return ngx.exit(429) -- 频率超限
end
ngx.log(ngx.ERR, "limit err: ", err)
return ngx.exit(500)
end
}
proxy_pass http://backend;
}
- 支持按用户 ID、商品 ID 或 IP 动态构造 key,实现更细粒度控制(如每个用户每分钟最多抢 5 次):
local key = "user:" .. user_id
用 shared dict 手写库存扣减(带原子性保障)
秒杀本质是“库存递减 + 状态检查”,必须保证多 worker 同时操作不超卖。不能依赖后端数据库锁,得在 OpenResty 层完成原子判断与扣减。
- 定义库存字典(如商品 1001 库存初始 100):
- 在 Lua 中安全扣减(利用
incr的原子性):
local sku_id = "1001"
local remain = stock:get(sku_id)
if not remain or remain return ngx.exit(400)
end
local new_remain = stock:incr(sku_id, -1)
if new_remain == nil then
-- incr 失败(比如 key 不存在或不是 number),回退
return ngx.exit(500)
end
if new_remain -- 已扣成负数,说明刚好被多个请求同时抢完,需恢复并拒绝
stock:set(sku_id, 0)
return ngx.exit(400)
end
结合 Redis 做分布式协同(可选增强)
当集群节点多、共享字典无法覆盖全部机器时,可用 Redis + Lua 原子脚本兜底。OpenResty 通过 resty.redis 调用,但要注意连接池和超时设置:
- 用 Redis 的 EVAL 执行库存扣减脚本(避免网络往返中的竞态):
local red = redis:new()
red:set_timeout(100)
local ok, err = red:connect("redis-host", 6379)
if not ok then return ngx.exit(500) end
local res, err = red:eval(
"if redis.call('exists', KEYS[1]) == 0 then return -1 end "
.."local v = redis.call('get', KEYS[1]) "
.."if tonumber(v) .."return redis.call('decr', KEYS[1])",
1, "stock:1001"
)
- 建议仅用于跨机房或大促压测阶段;日常高并发下优先用 shared dict,性能差一个数量级
防刷与请求合法性校验(前置过滤)
限流不是万能的,恶意请求会提前耗尽配额。应在 access 阶段加入轻量校验:
- 验证签名或时间戳(防止重放)
- 检查 UA、Referer 是否异常(简单黑白名单)
- 对高频 IP 做临时封禁(用另一个 shared dict 记录触发次数)
- 前端提交需带动态 token(服务端签发,一次有效),避免脚本批量请求
这些逻辑都放在 access_by_lua_block,失败直接 ngx.exit(403),不进后续流程。











