NOSCRIPT是Redis脚本缓存清空后的正常响应,因缓存纯内存、不持久、不跨节点,重启/主从切换/SCRIPT FLUSH均导致SHA失效;客户端必须捕获该错误并自动fallback至EVAL重新加载,而非忽略或重试EVALSHA。

单机Redis故障时,Lua锁脚本本身无法“确保”安全释放——因为锁已随实例宕机而丢失,所谓“释放”已无意义。真正要保的,是故障恢复后不出现误释放、不破坏互斥性。
为什么EVALSHA在Redis重启后必然报NOSCRIPT
Redis的Lua脚本缓存是纯内存字典,实例重启即清空。你之前用EVALSHA执行过的脚本SHA值(比如"a1b2c3")在新实例里根本不存在,Redis直接返回NOSCRIPT No matching script。这不是脚本写错了,是缓存没了。
- 主从切换、
SCRIPT FLUSH、运维扩缩容都会触发缓存丢失 -
EVALSHA不带降级逻辑 = 生产环境定时炸弹 - 别依赖“测试环境没出过问题”——缓存命中的偶然性掩盖了设计缺陷
必须实现EVAL fallback机制,且仅限首次加载
不能每次释放都走EVAL(网络开销大、可能触发maxmemory淘汰),但必须在NOSCRIPT时自动切回EVAL并重新加载脚本。关键点:
- 客户端需捕获
NOSCRIPT错误,而不是把它当业务异常吞掉 - 重试时用
EVAL提交完整脚本,Redis会自动计算SHA并缓存 - 后续调用继续用
EVALSHA,避免重复加载 - SDK如
redis-py的evalsha方法默认不带fallback,得自己封装一层
锁value唯一性失效比脚本丢失更致命
Redis单机故障期间,所有锁key都被清空。恢复后,如果客户端仍用旧lockValue(比如硬编码UUID或复用进程ID),就可能和别的客户端撞上——这时即使Lua脚本执行成功,也删的是别人刚设的锁。
- 每次加锁前必须生成全新
lockValue,推荐UUID4 + 随机数组合 - 禁止用时间戳、PID、主机名等可预测值作为value
- value长度建议≥32字符,降低碰撞概率
- 若用连接池,确保不同线程/协程不共享同一
lockValue
最易被忽略的一点:Redis单机故障后,你没法靠Lua脚本“修复”已发生的并发;能做的只是让恢复后的锁行为不雪上加霜——这要求脚本加载逻辑健壮、value生成逻辑不可预测、客户端对NOSCRIPT有明确处置路径,三者缺一不可。











