NOSCRIPT是Redis协议层明确返回的错误,非网络问题;因脚本缓存仅存于内存、不持久化、不跨节点同步,实例重启、集群路由错配、主从切换或连接池重建均会导致SHA1查不到,必须通过SCRIPT EXISTS实测确认。

为什么EVALSHA突然报NOSCRIPT,而不是连接失败或超时?
因为这是 Redis 协议层返回的明确错误响应,不是网络或超时问题。服务端收到 EVALSHA 后,只用传入的 SHA1 值查内存字典;查不到就立刻返回 NOSCRIPT No matching script. Please use EVAL.,不尝试加载、不重试、不兜底。所以看到这个错误,第一反应不该是“连不上 Redis”,而是“这个 SHA 对应的脚本在当前节点上根本没加载过”。
常见诱因包括:
- Redis 实例刚重启,所有脚本缓存清空
- 集群环境下只对某个节点执行了
SCRIPT LOAD,而请求路由到了其他节点 - 主从切换后,新主节点没继承旧主的脚本缓存(Redis 不同步脚本缓存)
- 客户端用了连接池,但连接被回收后重建,新连接对应的新连接上下文里没预热脚本
- 运维误执行了
SCRIPT FLUSH或启用了maxmemory-policy触发的 LRU 清除(虽罕见,但可能)
怎么确认脚本是否真的在目标节点上?
别猜,直接查。用 redis-cli 连到报错的那台 Redis 实例(注意:必须连具体出问题的节点,不是哨兵地址、不是集群代理),然后执行:
SCRIPT EXISTS <your_sha1>
如果返回 (integer) 0,说明该节点确实没这个脚本;返回 (integer) 1 才表示存在。再补查一次:
SCRIPT LOAD "your_lua_script_here"
看是否能成功返回同一 SHA1 —— 如果返回值不同,说明脚本内容有细微差异(比如换行、空格、注释),SHA1 就会变。
关键点:
-
SCRIPT EXISTS必须在出问题的节点上执行,集群/哨兵场景下不能只查一个 - 脚本字符串必须和代码中完全一致(包括不可见字符),否则
SCRIPT LOAD会生成新 SHA - 不要依赖客户端日志里的 SHA 是否“看起来一样”,要用
SCRIPT EXISTS实锤
Java 客户端(Lettuce/Redisson/Jedis)最容易踩的坑
不是脚本写错了,是调用链路断在了“缓存感知”环节。各客户端行为差异很大:
-
Jedis默认不自动回退:调evalSha()失败就抛JedisDataException: NOSCRIPT,得自己 catch 后手动 fallback 到eval() -
Lettuce的RedisScript支持setAutoRecover(true),但仅限单机模式;集群下需配合ClusterClientOptions.autoReconnect(true)+ 自定义CommandListener拦截NOSCRIPT -
Redisson开启useScriptCache = true后,会在首次调用时自动SCRIPT LOAD,但前提是脚本对象被正确复用;若每次 new 一个RedisScript,缓存就失效 - Spring Data Redis 的
execute(RedisScript, ...)默认走的是EVALSHA,但不内置降级逻辑,需自行包装
最常被忽略的一点:连接池初始化时没触发预加载。比如用 HikariCP + Lettuce,连接池创建完,但没人调用 scriptLoad(),第一个请求就必然 NOSCRIPT。
生产环境必须做的三件事
靠“运气不报错”不如靠机制保命。这三步缺一不可:
- 服务启动时,用
SCRIPT LOAD主动把所有业务脚本加载进每个 Redis 节点(单机/主从/集群都要覆盖) - 所有
EVALSHA调用必须包裹 fallback:捕获NOSCRIPT异常后,立即用完整脚本走EVAL,并异步重新SCRIPT LOAD到当前连接所连节点 - 监控加一层:对
SCRIPT EXISTS命令做定期探活(比如每分钟抽检 3 个关键 SHA),发现返回 0 就告警,而不是等业务报错才感知
复杂点在于“当前连接所连节点”——集群模式下,一次 EVALSHA 可能路由到任意节点,fallback 时的 EVAL 和后续 SCRIPT LOAD 必须发生在同一连接上下文里,否则下次还是 NOSCRIPT。这点很多 SDK 文档根本没提,只能看源码或实测验证。


















