openresty通过lua脚本+redis实现幂等性校验:在access_by_lua_block阶段用idempotency-key查redis,命中success则直接返回缓存响应;未命中则写processing状态放行;log_by_lua_block中根据ngx.status更新success并存响应体。

OpenResty 通过 Lua 脚本实现请求去重与幂等性校验,核心思路是:在请求进入业务逻辑前,用唯一标识(如 Idempotency-Key 请求头)查缓存;若已存在成功响应,则直接返回,避免重复处理。
使用 Idempotency-Key + Redis 实现幂等控制
客户端每次发起幂等请求时,带上自生成的、全局唯一的 Idempotency-Key(如 UUID)。OpenResty 在 access_by_lua_block 阶段读取该 key,查询 Redis 中是否已有对应的成功结果:
- 命中且状态为
success→ 调用ngx.exit(200)并透传缓存的响应体(需提前存好) - 命中但状态为
processing→ 可选择返回409 Conflict或阻塞等待(不推荐),或直接拒绝 - 未命中 → 写入
processing状态,继续后续流程;业务成功后,在log_by_lua_block中更新为success并存响应体
关键 Lua 代码片段(基于 resty.redis)
以下为精简可落地的逻辑骨架(需配合 init_by_lua_block 初始化 Redis 连接池):
-- access_by_lua_block
local idempotency_key = ngx.req.get_headers()["Idempotency-Key"]
if not idempotency_key then
return ngx.exit(400)
end
<p>local red = redis:new()
red:set_timeout(1000)
red:connect("127.0.0.1", 6379)</p><p>-- 查缓存:key = "idempotent:" .. idempotency_key
local res, err = red:hgetall("idempotent:" .. idempotency_key)
if not res then
-- 未命中:写 processing 状态,允许执行
red:hset("idempotent:" .. idempotency_key, "status", "processing")
red:expire("idempotent:" .. idempotency_key, 300) -- 5分钟过期防堆积
else
if res.status == "success" then
-- 直接返回缓存结果
ngx.header.content_type = res.content_type or "application/json"
ngx.status = tonumber(res.status_code) or 200
ngx.say(res.body)
ngx.exit(ngx.HTTP_OK)
else
ngx.exit(409) -- processing 或其他异常状态
end
end</p>在 log_by_lua_block 中安全落库结果
业务处理完成(无论成功或失败)后,在 log_by_lua_block 中更新 Redis 状态。注意只对成功响应写 success 并保存 body:
- 用
ngx.ctx在 access 和 log 阶段传递idempotency_key,避免重复解析 - 检查
ngx.status是否为 200/201 等成功码,再写入body(需限制大小,如 ≤1MB) - 失败情况可写
failed状态并记录错误码,防止重试无限循环
注意事项与边界处理
实际部署中需关注几个易错点:
- Redis 单点故障会影响幂等性,建议用哨兵或 Cluster 模式
- Idempotency-Key 不应由服务端生成,必须由客户端提供并保证重试时复用
- GET 请求天然幂等,无需加此逻辑;DELETE/POST/PUT 等非幂等方法才需校验
- 响应体过大时不要全量缓存,可只存摘要或关键字段,业务侧自行重建
- 超时请求可能造成 Redis 中残留
processing状态,需配后台清理任务或合理设 TTL











