SCRIPT KILL 仅在脚本未执行写命令时生效;一旦调用 SET 等写操作,脚本进入不可中断状态,此时返回 BUSY 错误。

为什么 SCRIPT KILL 有时不生效?
当 Lua 脚本在 Redis 中陷入死循环或执行过久,SCRIPT KILL 并非总能立刻终止它——前提是该脚本**尚未执行到写命令(如 SET、HSET)**。Redis 只允许在只读阶段中断脚本;一旦触发了任何写操作,脚本就进入「不可中断」状态,此时 SCRIPT KILL 会返回错误:(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
- 判断依据:观察
redis-cli INFO commandstats中cmdstat_eval的calls和usec_per_call,若后者持续飙升且无下降趋势,大概率已卡住 - 只读脚本(仅用
GET、EXISTS、redis.call("GET", ...)等)可被安全SCRIPT KILL - 含
redis.call("SET", ...)或redis.pcall写操作的脚本,SCRIPT KILL会拒绝执行,只能等它自然结束或重启 Redis(不推荐)
如何提前规避 Lua 死循环?
Redis 对 Lua 脚本有内置保护机制,但依赖你主动启用和配合。关键不是“事后杀”,而是“事前防”。
- 启动时务必设置
lua-time-limit(单位毫秒),例如redis-server --lua-time-limit 5000,默认是 5000,超时后自动标记为 BUSY,允许SCRIPT KILL - 脚本内避免裸写
while true do ... end,改用带计数器的有限循环:for i = 1, 1000 do ... if i >= 1000 then break end end - 慎用
redis.call()在循环体内反复查库,每次调用都计入时间统计;高频访问应先local keys = redis.call("KEYS", "prefix:*")批量取回再本地处理 - 测试阶段用
redis-cli --eval script.lua , key1 key2配合DEBUG SLEEP 0.1模拟耗时,验证超时响应
SCRIPT KILL 的正确执行流程
不是输入命令就完事,顺序和上下文决定成败。
- 先确认目标实例状态:
redis-cli INFO | grep -E "(loaded|busy|lua)",重点看lua_script_busy是否为1,以及used_memory_rss_human是否异常上涨 - 连接同一实例后立即执行:
redis-cli SCRIPT KILL(无需参数),不要加空格或引号 - 如果返回
OK,说明成功中止;若返回NO SCRIPT,表示当前无运行中脚本;返回BUSY错误,则脚本已进入写阶段,无法 kill - 注意:集群模式下,必须连接到**正在执行该脚本的具体分片节点**,不是任意一个入口节点
比 SCRIPT KILL 更可靠的兜底方案
生产环境不能押注单点命令。真正稳的做法是组合控制。
- 在应用层对
EVAL/EVALSHA调用加超时(如 Go 的context.WithTimeout,Java Lettuce 的timeout配置),让客户端主动断连,减轻服务端压力 - 监控侧采集
INFO stats中的rejected_connections和expired_keys突增,往往伴随 Lua 卡顿 - 对高风险脚本做沙箱预演:用
redis-server --port 0 --lua-time-limit 100启一个临时实例,跑脚本看是否触发超时 - 永远保留一份可降级的非 Lua 实现(比如用客户端分批
GET+ 本地计算),一旦发现频繁SCRIPT KILL就切走
最常被忽略的一点:Lua 脚本里的 redis.log() 不计入执行时间统计,但它会阻塞 I/O —— 日志太多本身就能拖慢脚本,别把它当调试神器无节制用。


















