lua-time-limit 不是超时中断开关,仅控制 script kill 是否可用;它不自动中断脚本,仅在单次 redis.call 上下文超时时允许手动 kill,且对已执行写命令的脚本无效。

lua-time-limit 不能强制中断脚本,它只决定 SCRIPT KILL 是否可用。 真正能防止集群瘫痪的,是组合配置 + 编码约束 + 客户端兜底,而不是单靠调大或调小这个值。
为什么 lua-time-limit 不是“超时中断开关”
这个配置写在 redis.conf 里,单位毫秒,默认 5000。它的实际作用是:当脚本在单次 redis.call() 上下文内连续执行超过该时间,Redis 就认为“可能出问题了”,并允许你发 SCRIPT KILL 命令去尝试中止——但它不会自动 kill,也不会中断正在跑的循环。
- 脚本里写
for i = 1, 1000000 do end(没调任何redis.call),会卡死在单次计时器里,触发超时后返回BUSY,连接被断开,但脚本线程仍占用 CPU - 脚本里写
while true do redis.call("PING") end,每次redis.call都重置计时器,可能永远不触发超时 - 集群模式下,每个 master 节点独立判断超时,
lua-time-limit配置必须在所有节点一致,否则某节点超时而其他节点没超,状态不一致风险放大
SCRIPT KILL 为什么经常失效
SCRIPT KILL 只对“尚未执行任何写命令”的脚本有效。一旦出现 redis.call("SET", ...) 或 redis.call("DEL", ...),它就拒绝执行,并报错:(error) UNKILLABLE Sorry the script already executed write commands...
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 常见误判:以为加了
lua-time-limit就等于“脚本能被安全终止”,结果上线后一个带INCR的限流脚本卡住,SCRIPT KILL直接失败 - 真实后果:该节点进入 BUSY 状态,所有新请求返回
BUSY Redis is busy running a script,客户端若无重试/降级逻辑,立刻雪崩 - 此时唯一选择是
SHUTDOWN NOSAVE,但这是整节点重启,集群会触发 failover,可能引发 slot 迁移、client 重连风暴
真正防瘫痪的三件套配置
光靠服务端参数不够,必须客户端和服务端协同设防:
- 服务端:把
lua-time-limit设为2000(2 秒),比默认更激进;同时开启notify-keyspace-events Ex,用于外部监听异常脚本持有的 key 并告警 - 客户端:所有 Lua 调用必须包裹
context.WithTimeout(ctx, 2500 * time.Millisecond)(Go)或等效 socket timeout,确保在服务端超时前主动断连并降级 - 上线前:用
redis-cli --eval+timeout 3包一层,在测试环境实测脚本最大耗时;对遍历类操作强制加守卫,例如if #KEYS > 100 then return error("too many keys") end
集群场景下最容易被忽略的点
集群不是单机的简单复制。一个看似安全的脚本,在集群里可能因数据分布和重定向逻辑放大风险:
-
EVAL脚本里如果用了redis.call("GET", KEYS[1])和redis.call("SET", KEYS[2]),但KEYS[1]和KEYS[2]不在同一个 slot,Redis 会直接报CROSSSLOT错误——这本身不危险,但很多客户端没正确处理该错误,导致重试风暴 - 脚本里调用
redis.call("CLUSTER NODES")或redis.call("INFO", "cluster")属于高危行为,这些命令在集群模式下开销极大,极易触达lua-time-limit - 别信“脚本已用
SCRIPT LOAD预加载就安全”——SCRIPT LOAD只校验语法,不校验运行时行为;EVALSHA才是真战场,必须对每个 SHA 做最小数据集压测










