redis 5.0 主从切换后 lua 脚本“失效”的根本原因是 eval 命令默认以指令复制方式同步,从节点未预加载脚本,导致 evalsha 报 noscript 错误;启用 lua-replicate-commands yes 可强制先 script load 再 evalsha,确保脚本体在从节点存在。

Redis 5.0 主从切换后 Lua 脚本不会“丢失”,但可能因复制机制不一致导致执行失败或结果不符——根本原因不是脚本本身消失,而是 EVAL 命令在主从间以“指令复制”(而非“脚本复制”)方式同步,且从节点未提前加载脚本。
为什么主从切换后 Lua 脚本看似“失效”
Redis 5.0 默认使用指令复制(replica-serve-stale-data yes + repl-backlog-size),即主节点把 EVAL "..." N key1 key2 这条命令原样发给从节点执行,而不是把脚本体先 SCRIPT LOAD 到从节点。这意味着:
- 如果主节点刚执行过一次
EVAL,从节点能复现结果(因为指令被完整复制并执行) - 但如果主节点用
EVALSHA执行一个未在从节点注册的 SHA1,从节点会返回NOAUTH或NOSCRIPT错误 - 主从切换后,新主节点(原从节点)自身没执行过该脚本,
SCRIPT EXISTS查不到 SHA,EVALSHA必然失败 - 客户端若只依赖
EVALSHA且无 fallback 到EVAL的逻辑,就会报错
必须启用 script-loading 复制才能保障一致性
Redis 5.0 支持通过配置开启脚本体的显式复制,让从节点在收到 EVAL 前先自动执行 SCRIPT LOAD。关键配置项是:
在主节点 redis.conf 中设置:lua-replicate-commands yes
这个参数的作用是:当启用时,Redis 会把每次 EVAL 拆成两步复制 —— 先发 SCRIPT LOAD(含完整脚本字符串),再发 EVALSHA。这样从节点始终拥有脚本体,主从切换后也能直接执行 EVALSHA。
注意:
• 该选项仅对 EVAL 生效,不影响已存在的 EVALSHA 调用
• 必须主从都运行 5.0+ 且配置一致,否则从节点忽略该 flag
• 若关闭(默认为 no),则完全依赖指令复制,风险如上所述
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
客户端必须实现 EVAL / EVALSHA 双路 fallback
即使服务端开启了 lua-replicate-commands yes,生产环境仍需客户端兜底。常见错误写法是硬编码调用 EVALSHA 并假设脚本一定存在。
安全做法应为:
- 首次调用某脚本时,先发
SCRIPT LOAD获取 SHA,缓存到本地(如内存 Map 或 Redis 自身的SETNX兜底) - 后续统一走
EVALSHA,但捕获NOSCRIPT错误 - 捕获到
NOSCRIPT后,立即重试SCRIPT LOAD+EVALSHA(不是回退到EVAL,避免重复传输大脚本) - 避免在事务(
MULTI/EXEC)中混用EVAL和EVALSHA,Cluster 模式下可能跨 slot 导致失败
Redis Cluster 下还有额外限制要绕开
如果你用的是阿里云 Redis 版(5.0.8 以下)或原生 Redis Cluster,EVAL 脚本中的所有 key 必须落在同一哈希槽,且必须通过 KEYS[1] 等显式引用,不能拼接、不能用 ARGV 传 key,否则报错:-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array
典型翻车点:
-
redis.call('get', 'foo')→ 错,key 字符串字面量不被允许 -
redis.call('get', ARGV[1])→ 错,ARGV 不是 key 来源 -
local k = KEYS[1] .. ':sub'; redis.call('get', k)→ 错,动态构造 key 不被识别为合法 key - 正确写法只有:
redis.call('get', KEYS[1]),且调用时EVAL "...", 1 foo
这个限制和主从切换无关,但在切换后因连接池重连、客户端重试逻辑混乱,更容易暴露出来。










