redis lua脚本天然适合幂等控制,因其eval/evalsha原子执行、无需加锁;需用redis.call("set",keys[1],argv[1],"ex",argv[2],"nx")实现带过期的业务id级幂等写入,禁用get+set等非原子操作,并通过script load预热+ evalsha复用脚本。

为什么 Redis Lua 脚本天然适合做幂等控制
因为 EVAL 和 EVALSHA 是原子执行的,整个脚本在服务端一次跑完,不会被其他命令插队。你不需要靠加锁、事务或外部状态同步来保序——只要脚本逻辑写对,SET key value EX 60 NX 这种“存在就失败”的语义就能卡住重复请求。
但注意:Lua 脚本里不能调用带随机性或时间依赖的命令(比如 TIME、RANDOMKEY),也不能发起网络请求;否则会破坏主从复制一致性,Redis 直接拒绝加载。
如何用 redis.call("set", ...) 实现带过期的幂等写入
常见错误是直接用 SET key val EX 300 NX 命令,但它只能判断 key 是否存在,无法校验「这次请求的业务参数是否和上次一致」。真正要防的是「相同业务 ID 的重复提交」,不是「key 是否已存在」。
实操建议:
- 把业务唯一标识(如
order_id)作为 key,把请求体摘要(如md5(json))作为 value 存进去 - 用
redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2], "NX"),其中ARGV[2]是 TTL 秒数,"NX"保证只写一次 - 脚本返回值用
if res == nil then return 0 else return 1 end,让客户端靠返回值判断是否首次执行
示例脚本:
if redis.call("set", KEYS[1], ARGV[1], "EX", ARGV[2], "NX") then
return 1
else
return 0
end
为什么不能直接用 GET + SET 拆成两步
因为这不是原子操作。两个并发请求可能同时 GET 到空值,然后都执行 SET,结果就是双写——幂等性彻底失效。
另一个坑是误用 EXISTS 判断后再 SET,同样非原子;还有人想用 INCR + 判断数值是否为 1,但没考虑初始化竞争,第一次 INCR 可能返回 1,第二次也返回 1(如果 key 刚被删又重建)。
必须用单条命令或一个 Lua 脚本包住读+写的全部逻辑。
怎么安全复用脚本避免每次传大段 Lua 字符串
频繁传 Lua 脚本字符串有开销,也容易因换行/空格差异导致 EVALSHA 命中失败。正确做法是先用 SCRIPT LOAD 预热,拿到 SHA1 值,后续全用 EVALSHA。
实操要点:
- 上线前用
redis-cli --eval script.lua key , arg1 arg2测试脚本逻辑 - 应用启动时执行一次
SCRIPT LOAD,缓存返回的 SHA 值(比如存在本地static final String SHA = "a1b2c3...") - 运行时优先调
EVALSHA,若返回"NOSCRIPT"错误,再 fallback 到EVAL并重载 SHA 缓存
别省这一步——线上 Redis 集群里脚本没预热,某次发布后所有请求都会触发 NOSCRIPT,瞬间打满连接。
真正的难点不在写脚本,而在于 key 的设计粒度、TTL 时长取舍、以及 fallback 机制是否真能兜住冷启动场景。这些不压测根本看不出问题。










