noscript错误源于redis未缓存对应sha,仅重试evalsha无效;必须捕获后执行script load获取新sha并更新本地缓存,尤其在集群、哨兵切换或连接池复用场景下需主动预热脚本。

直接结论:NOSCRIPT 不是脚本写错了,而是 Redis 服务端压根没缓存你传的 SHA —— 客户端必须捕获它,并 fallback 到 SCRIPT LOAD + EVAL 或纯 EVAL,不能只靠重试 EVALSHA。
为什么捕获 NOSCRIPT 后不能只重试 EVALSHA
重试 EVALSHA 只会再次触发相同错误。因为 SHA 对应的脚本在服务端仍不存在,而客户端缓存的 SHA 值不会自动刷新。常见错误模式包括:
- 硬编码
SCRIPT_LOAD返回的 SHA(如"e0e1f9df1fa3a6dd7c1e3b7f2e9f5e63b71da9f7"),Redis 重启或哨兵切换后该值立即失效 - 用
EVALSHA发起请求前,未确认目标节点是否已执行过SCRIPT LOAD(尤其在集群或分片环境下) - 客户端复用连接池中的旧连接,而该连接曾连过旧主库,脚本缓存在那边,新主库上为空
Java Jedis 中推荐的容错流程
以 Jedis 为例,标准做法是:先 evalsha → 捕获 JedisNoScriptException → 执行 scriptLoad 获取新 SHA → 再 evalsha。但注意两个关键点:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 必须用
scriptLoad显式加载,不能只靠eval:因为eval虽然也会注册脚本,但不返回 SHA,后续无法复用evalsha - 加载后要更新本地缓存的 SHA 值,否则下次请求仍用旧 SHA,继续报错
- 若脚本内容不变,
scriptLoad多次调用无副作用,返回值始终是同一 SHA,可安全重入
Spring Data Redis 的默认行为与陷阱
RedisTemplate.execute() 默认使用 ScriptExecutor,它内部已实现 NOSCRIPT 自动 fallback 到 eval。但这个机制有隐藏限制:
- 仅对首次调用生效:第一次
evalsha失败 →eval执行并注册脚本 → 后续请求可用evalsha - 不解决跨节点问题:在 Redis Cluster 中,
evalsha请求可能路由到未加载脚本的节点,而 fallback 的eval仍发往同一节点——该节点缓存了脚本,但其他节点没有,下次请求若路由到别处,依然 NOSCRIPT - 不处理哨兵切换:新主库启动时脚本缓存为空,
RedisTemplate不感知主从变化,不会主动预热
真正“优雅”的底线是控制权移交客户端
最稳妥的做法不是依赖框架自动 fallback,而是把脚本生命周期管理收归应用层:
- 服务启动时,对所有 Redis 实例(含集群各 master、哨兵下的候选主库)批量执行
SCRIPT LOAD,并缓存 SHA 到内存 Map 中 - 每次执行前,从本地 Map 取 SHA;若取不到或执行报
NOSCRIPT,则重新SCRIPT LOAD并更新 Map - 避免任何硬编码 SHA,所有 SHA 必须运行时生成并动态维护
复杂点在于:脚本缓存不是全局一致的,它依附于每个 Redis 实例的内存状态。只要没做显式同步,不同节点之间就天然不同步——这是 Redis Lua 设计决定的,绕不开。










