EVALSHA 并非天然更快,而是需预加载脚本(SCRIPT LOAD)后才生效;未预加载、节点不匹配或连接重置均导致 NOSCRIPT 错误,安全做法是服务启动时主动注册并复用 Script 对象。

EVALSHA 不是“用了就快”,而是“用对了才快”——它省的是脚本体的传输字节,但前提是 SHA 值必须已存在、且在正确节点上。
为什么 EVALSHA 会返回 “NOSCRIPT” 错误
这不是 Lua 写错了,是 Redis 根本没见到这个脚本。EVALSHA 只传 40 字符的 SHA1,Redis 拿它当钥匙去缓存里找编译好的字节码;钥匙对不上,就直接报 NOSCRIPT。
- 常见触发场景:新连接建立后首次调用、Redis 实例重启、集群中只对某一个节点执行过
SCRIPT LOAD - 连接池复用时尤其危险:你以为脚本“还在”,其实连接被回收后重连,缓存已清空
- 集群环境下,
SCRIPT LOAD必须发到目标 key 所在 slot 的节点,否则EVALSHA在其他节点执行必然失败
如何安全地预加载脚本(SCRIPT LOAD)
别等运行时再加载,上线前或服务启动阶段批量注册,把“加载”从热路径里摘出去。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
SCRIPT LOAD是幂等操作,重复执行返回相同 SHA,可放心在初始化逻辑里多次调用 - 脚本内容必须严格一致:换行、空格、注释都会改变 SHA,建议 CI 阶段用
lua-minify或固定格式化工具统一输出 - 不要硬编码 SHA 值:不同 Redis 编译版本或 Lua 解析器微调可能导致 SHA 变化,运行时计算最稳
redis-py 中 register_script 的真实行为
它不是“注册一次永久有效”,而是一个带自动 fallback 的封装:先试 evalsha,捕获 NoScriptError 后立刻 script_load 再重试。
- 这个 fallback 机制掩盖了加载时机问题——每次异常都补一次加载,反而放大了首次延迟
- 高并发下多个线程同时触发 fallback,可能造成重复
SCRIPT LOAD(虽幂等,但浪费连接和解析) - 真正推荐做法:服务启动时主动调用
r.register_script(...),并确保返回的Script对象被复用,而不是每次请求都新建
集群环境下的 EVALSHA 路由陷阱
KEYS 数组决定路由,但 SCRIPT LOAD 不走 key 路由——它只作用于当前连接的节点。这是最容易被忽略的断裂点。
- 如果脚本涉及多个 key(比如
KEYS[1]和KEYS[2]),它们必须落在同一 slot,否则EVAL都会报CROSSSLOT,更别说EVALSHA - 跨 slot 场景下,只能拆成多个单 key 脚本,或用 hash tag 强制 key 落在同一 slot(如
{user}:123和{user}:456) - 使用 Lettuce 时,开启
ScriptExecutor可自动同步脚本到所有节点;redis-py 则需手动遍历集群节点调用SCRIPT LOAD
最常被绕开的复杂点:脚本逻辑本身不能“动态”——没有配置读取、没有时间判断、没有随机数,所有分支只能靠 KEYS 和 ARGV 输入驱动。所谓优化,本质是把“变”的部分参数化,把“不变”的部分固化为 SHA。


















