Redis Lua脚本执行超时由客户端socket超时和服务器端lua-time-limit共同控制;前者是第一道防线,后者仅决定SCRIPT KILL触发时机,不强制中断脚本。

Redis Lua脚本执行超时由谁控制?
Redis 本身不提供 SCRIPT LOAD 或 EVAL 的超时参数,超时行为完全取决于客户端连接的 socket 超时设置和 Redis 的 lua-time-limit 配置。脚本一旦开始执行,就进入单线程原子运行状态,Redis 不会中途中断它——除非它自己跑超时了。
所以「防止超时」不是靠加 timeout 参数,而是靠两件事:限制脚本最大允许运行时间(服务端),以及确保客户端不因等待太久而断连(客户端)。
-
lua-time-limit是毫秒值,默认 5000(5 秒),它只影响SCRIPT KILL的触发时机,不是硬中断开关 - 脚本真超时后,Redis 不会自动 kill,必须手动发
SCRIPT KILL;若脚本已执行写操作,则只能用SHUTDOWN NOSAVE(危险!) - 客户端 socket 超时(比如 Python 的
socket.timeout、Go 的context.WithTimeout)才是你实际能抓到“超时异常”的第一道防线
怎样安全地用 SCRIPT LOAD + EVAL 组合?
SCRIPT LOAD 只是缓存脚本 SHA1,不执行,也不受 lua-time-limit 约束;真正风险在 EVALSHA 执行阶段。很多人误以为 SCRIPT LOAD 成功就等于脚本“没问题”,其实只是语法校验通过而已。
- 加载后务必用
EVALSHA测试一次空输入或最小数据集,确认不会卡住 - 不要在高并发路径里反复
SCRIPT LOAD——它不幂等,重复加载会浪费内存,且返回相同 SHA1,但 Redis 不去重 - 如果脚本依赖 KEYS/ARGV 中的数据量,必须在 Lua 里做
if #KEYS > 100 then return error("too many keys") end这类守卫,否则遍历大集合极易超时 - 避免在脚本里调用
redis.call("KEYS", "*")或redis.call("SCAN", ...),这些命令本身慢,叠加 Lua 解释器开销更易触达lua-time-limit
lua-time-limit 设太小或太大分别有什么后果?
这个配置写在 redis.conf 里,或通过 CONFIG SET lua-time-limit 2000 动态调整,但它不是“执行时限”,而是“允许脚本运行多久后可以被 SCRIPT KILL”。
- 设太小(如 100):正常复杂脚本(比如批量更新带条件判断的 50 个 key)可能还没跑完就被判定“可杀”,但此时
SCRIPT KILL仍失败(因已修改数据),反而卡死连接 - 设太大(如 30000):一个失控脚本能霸占主线程半分钟,所有其他命令排队等待,QPS 断崖下跌,监控指标(如
used_cpu_sys)会明显上扬 - 合理值取决于你的最重脚本实测耗时 × 1.5,建议先在从节点压测,观察
LATENCY LATEST和INFO commandstats中cmdstat_eval的usec_per_call
客户端怎么配合防超时?
服务端配置只是兜底,客户端才是第一响应者。很多超时问题其实出在客户端没设超时,或设得比 lua-time-limit 还长,导致 Redis 已经想 SCRIPT KILL 了,客户端还在傻等。
- Python redis-py:初始化 client 时传
socket_connect_timeout=3和socket_timeout=6,后者必须 >lua-time-limit(比如设 7000),留出 kill 和响应时间 - Node.js ioredis:用
timeout: 7000选项,同时监听error事件,捕获"ReplyError: BUSY Redis is busy running a script"后主动降级 - Java Lettuce:配置
timeoutOptions = TimeoutOptions.enabled(Duration.ofSeconds(7)),并检查是否返回RedisCommandTimeoutException - 所有客户端都应把
EVAL/EVALSHA包在带 cancel 的 context 里,超时后立刻 close 连接,避免连接池污染
最麻烦的其实是嵌套调用:A 脚本里 redis.call("EVALSHA", ...) 再调 B 脚本,此时总耗时是累加的,但 lua-time-limit 只看外层起点——这点很容易被忽略。










