跑一个失控的纯计算Lua脚本和一个先写数据再死循环的脚本,分别用SCRIPT KILL尝试终止,看Redis是怎么在中断脚本和保护原子性之间划界的。
上一篇看到脚本超过5秒后其它客户端会收到BUSY,但SCRIPT KILL/SHUTDOWN NOSAVE是仅有的两个例外。
代码script = """ local x = 0 for i=1,10000000000 do x = x + 1 end return x """ 线程A: eval(script)(这个循环量要跑几十秒) 主线程: sleep(5.5)秒后,从另一个连接发SCRIPT KILL
脚本本来要跑几十秒,只跑了5.5秒就被中断。
结果SCRIPT KILL response: True (即+OK) 原脚本这条EVAL收到的错误: Script killed by user with SCRIPT KILL... script: 18fca209ac12270..., on @user_script:3. busy script total time: 5.4998509883880615 秒 之后server responsive: True (立刻恢复正常)
验证了SCRIPT KILL能真正打断一个正在运行的Lua脚本——不是等它自然结束,是立刻终止,原本发起EVAL的那个连接会收到一条明确的"被用户用SCRIPT KILL杀死"的错误,Redis主线程立刻恢复响应其它请求。
换一个脚本:先SET一个key,然后再进入同样的死循环。
结果script2 = """ redis.call('SET', 'written_before_loop', '1') -- 先执行了一次写 local x = 0 for i=1,10000000000 do x = x + 1 end return x """ SCRIPT KILL response: 报错 UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
SHUTDOWN NOSAVE,直接放弃这次修改重启整个进程,这也是为什么写Lua脚本时,通常建议在脚本一开始就用redis.call做完所有真正的写操作,再做耗时的计算,而不是反过来。
每一步都是本文上面用SCRIPT KILL命令跑出来的原始结果。
SCRIPT KILL:对比纯计算脚本和含写命令脚本两种场景的杀死结果