script load失败则evalsha必返回noscript,因redis仅认已缓存的sha1;集群中需向所有相关slot主节点分别执行script load,且必须使用服务端返回的sha而非客户端计算值。

SCRIPT LOAD没执行成功,EVALSHA必然返回 NOSCRIPT
脚本预加载失败,EVALSHA 就不可能成功——这不是网络抖动或参数写错的问题,而是 Redis 的硬性机制:它只认自己缓存里已注册的 SHA1。一旦 SCRIPT LOAD 没走通(比如命令被拦截、连接中断、目标节点选错),后续所有对该 SHA 的 EVALSHA 都会原样抛出 NOSCRIPT No matching script. Please use EVAL.。
常见失效场景包括:
- 客户端用连接池复用连接,但没在每个新连接建立后补做
SCRIPT LOAD - 用了 Sentinel 或代理层,
SCRIPT LOAD被路由到非主节点,而EVALSHA发到了主节点 - 集群模式下只对一个节点执行了
SCRIPT LOAD,但脚本涉及的 key 分布在多个 slot,其他节点压根没见过这个 SHA
Redis集群中必须对所有相关节点执行 SCRIPT LOAD
Redis 集群不共享脚本缓存,每个节点独立维护自己的 SCRIPT EXISTS 结果。你不能指望在一个节点上 SCRIPT LOAD 之后,其他节点也自动“知道”。
正确做法是根据脚本中 KEYS 的实际分布,把 SCRIPT LOAD 命令发给所有涉及的哈希槽所在节点。例如:
- 脚本只操作
{user}:123和{user}:456,两个 key 都落在同一个 slot → 只需向该 slot 主节点执行一次SCRIPT LOAD - 脚本操作
user:123和order:789,它们 hash 后分属不同 slot → 必须分别向两个 slot 的主节点各执行一次SCRIPT LOAD - 用
redis-cli --cluster call批量下发(仅限运维期);生产代码中建议用支持自动分发的客户端,如redisson或ioredis的scriptLoad方法
客户端封装 evalsha_safe 是最稳妥的兜底方案
靠人工保证“先 load 再 sha”在复杂部署下极易漏掉环节。更可靠的方式是在调用层封装带 fallback 的执行函数。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
核心逻辑就是:先试 EVALSHA,捕获 NOSCRIPT 异常后,自动触发 SCRIPT LOAD + EVAL,并缓存新生成的 SHA(注意:EVAL 本身也会注册脚本,但不返回 SHA,所以仍需显式 SCRIPT LOAD 来获取)。
- Java 中
RedisTemplate.execute()默认就做了这层包装,但前提是没被中间件(如 Sentinel 流控模块)吃掉原始异常 - Python 的
redis-py需手动实现:用try/except捕获ResponseError,检查str(e)是否含"NOSCRIPT",再走 fallback - Node.js 的
ioredis提供script.load()和evalsha组合方法,但默认不自动重试,需自行 wrap
别依赖客户端缓存的 SHA 值跨环境复用
同一个 Lua 脚本字符串,在不同 Redis 版本、不同平台(Windows/Linux 行尾符)、甚至不同编码保存方式下,生成的 SHA1 可能不同。更隐蔽的是:某些客户端(如旧版 Jedis)在序列化脚本时会悄悄 trim 空格或 normalize 换行,导致本地算的 SHA 和服务器上 SCRIPT LOAD 返回的不一致。
所以:
- 永远用服务器返回的 SHA(即
SCRIPT LOAD的响应体),而不是客户端自己计算 - 不要把 SHA 写死在配置文件或数据库里,尤其当脚本内容可能更新时
- 上线新脚本前,务必在目标环境真实执行一次
SCRIPT LOAD,拿到它的返回值用于后续EVALSHA
最易被忽略的一点:脚本内容哪怕只多一个空格、少一个分号,SHA 就完全不同——而错误往往出现在你认为“脚本没变”的时候。










