bigywater.tech
Redis · 33

Lua 脚本阻塞主线程

跑一个纯CPU计算的Lua脚本,同时从另一个连接发PING,验证Redis单线程模型下脚本执行期间其它客户端经历的是静默等待还是明确报错,以及5秒阈值的意义。

2.2秒静默
未超5秒阈值时PING的等待方式(无错误)
5000ms
lua-time-limit的默认阈值
-BUSY
超过阈值后其它客户端收到的明确错误

做的事:一个纯CPU计算的Lua脚本,配一个同时发出的PING

上一篇看到Lua脚本本身省了网络round-trip很快,这篇看它慢的时候有多"霸道"。

代码script = """ local x = 0 for i=1,500000000 do x = x + 1 end return x """ 线程A: 发eval(script) 主线程: 0.1秒后,从另一个连接发PING

验证:脚本跑2.3秒(未超过5秒阈值),PING静默等待,没有任何错误

这才是真正违反直觉的地方——不是变慢,是完全没反应。

结果busy_time (脚本总耗时): 2.316365957260132 ping_wait (PING等了多久才收到响应): 2.219856023788452 ping_result: PONG normally (blocked silently, no error)

验证:PING从发出到收到PONG,等了2.2秒——几乎等于脚本本身的总耗时。这不是网络延迟,也不是超时重试,是Redis单线程模型最朴素的体现:脚本在执行的这段时间里,Redis的主线程完全被占用着,连读取PING这条命令本身的网络数据这件事都没有机会去做,PING命令是在脚本执行完、主线程腾出手来之后,才被读取、执行、回复的。

验证:脚本跑超过5秒,其它客户端会开始收到明确的BUSY错误

Redis有一个叫lua-time-limit的配置,本机默认值是5000毫秒。

config getlua-time-limit: 5000 结果(一个跑超过5秒的脚本): ping_wait: 4.900055170059204 ping_result: error: BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
机制是:脚本刚开始执行的头5秒,Redis主线程完全不理会任何其它客户端(跟上面的短脚本场景一样,静默等待);一旦脚本运行超过lua-time-limit这个阈值,Redis会开始抽空处理其它客户端的请求——但处理方式不是正常执行,而是对绝大多数命令直接回复一个-BUSY错误,明确告诉你"我正忙着跑一个脚本,你现在只能发SCRIPT KILL或者SHUTDOWN NOSAVE",脚本本身则继续在后台跑,不会自动停。的设计意图很清楚:Redis单线程没有办法真的"暂停"一个正在执行的Lua脚本去处理别的请求,5秒阈值只是给了运维一个信号——"这个脚本可能失控了,你可以考虑手动介入",而不是真的解除了阻塞。

分步演示:Lua脚本是怎么"锁死"主线程的

每一步都是本文上面跑出来的原始结果。

5秒之前静默阻塞,5秒之后才开始明确报BUSY
这一步新验证的场景

    本文用到的工具

    • 本机 Redis 8.10.1,config get确认lua-time-limit=5000
    • Python redis-py 8.0.1 + threading:测量CPU密集Lua脚本对并发PING的阻塞效果
    京ICP备2026067502号-1京公网安备11010502063487号