EVALSHA在Redis集群中报NOSCRIPT或MOVED,根本原因是路由错位:SCRIPT LOAD未发至KEYS[1]所在slot节点,且EVALSHA不携带KEYS数组导致Redis无法定位脚本和key,所有key还必须同属一个slot,否则触发CROSSSLOT错误。

Redis集群下Lua脚本执行失败或超时,90%以上是路由错位或KEYS声明不合规导致的,不是脚本本身慢。
为什么EVALSHA在集群里总报NOSCRIPT或MOVED?
集群模式下,Redis必须提前知道脚本访问哪些key,才能把请求路由到对应slot的master节点。但EVALSHA命令本身不携带KEYS数组——它只传SHA和参数,Redis无法自动推导key位置。
- SCRIPT LOAD必须发到
KEYS[1]所在slot的节点,否则返回(error) NOSCRIPT No matching script - EVALSHA执行时,
KEYS数组不能为空,且所有key必须属于同一slot(否则报CROSSSLOT) - 常见错误:脚本里写
"user:"..ARGV[1]拼接key名,Redis无法提取slot,直接拒绝执行 - 正确做法:客户端显式把所有涉及的key都列进
KEYS参数,哪怕只是读取也得声明
如何让一次GEOSEARCH+批量HGETALL真正压到单次RTT?
目标是把N+1网络往返变成1次,但前提是所有被HGETALL访问的成员key,都得提前出现在KEYS数组里——否则集群会把部分请求打到错误节点,返回ASK或MOVED。
- 先用
redis.call("GEOSEARCH", KEYS[1], ...)拿到成员列表,比如{"shop:1001", "shop:1002"} - 这些成员名本身也是key,必须全部传入
KEYS(不能只传主表locations一个) - 脚本里对每个成员调
redis.call("HGETALL", KEYS[i+1]),i从2开始递增 - 客户端调用时,
KEYS数组长度 = 1(主表)+ N(查出的成员数),numkeys参数必须严格匹配
lua-time-limit设成2000后还是卡死?检查这三处
lua-time-limit不是保险丝,它只允许你触发SCRIPT KILL,不主动中断。设成2000毫秒后仍卡死,大概率是以下情况:
- 脚本里有纯计算循环:
for i = 1, 1000000 do end完全绕过计时,CPU跑满也不触发超时 - 已执行写命令:
redis.call("SET", ...)之后再调SCRIPT KILL,返回(error) UNKILLABLE - 客户端连的是从节点或随机代理,
SCRIPT KILL发到了错误节点,实际脚本还在master上跑 - 集群中各master节点
lua-time-limit值不一致,超时判断逻辑分裂
EVALSHA缓存命中率低?别怪Redis,先看客户端怎么加载的
EVALSHA快的前提是脚本真被缓存了。但Redis 5.0+不会自动缓存,全靠客户端主动SCRIPT LOAD并复用SHA。
- Spring Boot + Lettuce默认不启用
ScriptExecutor,每次重启都重新LOAD,反而加重初始化压力 - 脚本内容多一个空格、换行或注释,SHA1就不同,
EVALSHA必然失败,必须用构建工具固化内容 - 上线前必须预热:用
redis-cli -c --cluster call {host}:{port} SCRIPT LOAD "..."确保每个master都加载过 - fallback逻辑要完整:
EVALSHA返回NIL时,必须立刻SCRIPT LOAD再重试,不能静默降级为EVAL
最易被忽略的点:集群下SCRIPT KILL不广播,你在节点A上执行,节点B上的同脚本副本依然在跑。真正可控的防线,是客户端超时(如Lettuce的CommandTimeout)比lua-time-limit早100ms断连,把风险掐死在发起阶段。



















