Redis Lua脚本中大循环会阻塞整个实例,因单线程执行且lua-time-limit对纯计算循环无效;应优先用原生命令批量操作,禁用循环内redis.call(),严格控制本地循环规模,并预加载SCRIPT LOAD确保EVALSHA安全执行。

Redis Lua 脚本里写大循环,等于直接锁死整个 Redis 实例——它单线程执行,一个脚本卡住,所有命令排队等。
为什么 for i=1,500 do redis.call('GET', ...) end 会拖垮服务
这不是“慢”,是“阻塞”。每次 redis.call() 都触发完整命令流程(解析、校验、路由、执行),500 次就是 500 倍开销;更糟的是,纯计算循环(比如 for i=1,1000000 do end)完全不触发 lua-time-limit 计时,超时配置形同虚设。现象是:INFO cpu 显示 used_cpu_sys 持续 >90%,但 instantaneous_ops_per_sec 却不高,新请求全返回 BUSY Redis is busy running a script。
必须用原生命令替代循环的三种典型场景
别在脚本里手动遍历,把批量能力还给 Redis 自己:
- 读多个字段:用
HMGET替代循环HGET,ZRANGEBYSCORE key min max WITHSCORES一次拉带分值的范围,而不是对每个 ID 调ZSCORE - 删/设多个 key:用
MSET或DEL批量传参,而非循环SET或DEL单 key - 条件过滤:只从 Redis 拉关键数据(如 ID + score),筛选逻辑放客户端;或改用
SSCAN/HSCAN分批,避免SMEMBERS一次性加载大集合到 Lua 内存
真要循环,必须加守卫且禁用 redis.call()
仅限极简、无副作用的本地计算,且必须控制规模:
- 循环上限强制用
math.min(50, #KEYS)或math.min(100, #ARGV),禁用while true和无界for - 循环体内禁止任何
redis.call()或redis.pcall(),只能做字符串拼接、数值累加、数组 push 等轻量操作 - 所有 Redis 读结果必须提前缓存到 local 变量,例如
local data = redis.call('LRANGE', KEYS[1], 0, 99),后续遍历data而非反复 call
EVALSHA + SCRIPT LOAD 不是可选,是保命底线
没预热的 EVALSHA 和裸用 EVAL 一样危险——每次重传、重解析脚本,CPU 白烧。必须:
- 服务启动时调用
SCRIPT LOAD,拿到 SHA1 后缓存在应用内存或配置中心 - 执行
EVALSHA后检查返回值,遇到NOSCRIPT必须 fallback 到SCRIPT LOAD+ 再次EVALSHA,不能退化成EVAL - 集群环境下,
SCRIPT LOAD必须发到目标 key 所在 slot 的 master 节点,proxy 或随机连节点会导致其他节点无此 SHA
最易被忽略的一点:脚本里用 redis.call('TIME') 取时间再做判断,返回的是秒级整数,精度不够,且每次调用都算一次完整命令开销;真正该做的是让客户端传毫秒时间戳进 ARGV,脚本里只做数值比较。


















