bigywater.tech
Redis · 38

Lua 脚本中的慢命令

在Lua脚本里只调用一条SORT命令(不写任何循环),对200万元素排序,验证脚本阻塞主线程的根源是脚本调用的Redis命令本身的时间复杂度,不是Lua代码写得多不多。

2.33秒
200万元素SORT本身的耗时
1.08秒
并发PING被拖慢的等待时间
命令复杂度才是根源
脚本行数不重要,调用的命令时间复杂度才决定阻塞时长

做的事:脚本里不写循环,只调用一条很慢的Redis命令

本系列前面验证过纯CPU循环会阻塞主线程,这次换成脚本里调用一条O(N)级别的命令。

代码先建一个200万元素的list(biglist),元素是随机整数字符串 script = "return redis.call('SORT', KEYS[1])" 线程A: eval(script, 1, "biglist") 主线程: 0.05秒后,从另一连接发PING

验证:SORT这一条命令本身就要2.33秒

脚本里只有一行redis.call,没有任何Lua层面的循环。

结果SORT-via-Lua total time: 2.328796863555908 elements: 2000000

验证:对200万元素做一次的SORT,本身就是一个O(NlogN)级别的排序操作,哪怕这条命令是Redis用C语言高效实现的,200万这个的数据规模下,耗时依然到了2.3秒这个量级——这跟"脚本是用Lua写的所以慢"完全没关系,脚本里唯一做的事就是调用这一条命令。

验证:SORT执行期间,并发的PING也被拖慢了

机制跟本系列讲Lua阻塞主线程那篇的纯计算循环完全一样。

结果concurrent PING wait time: 1.0841500759124756 ping ok: True
结论收束了本系列关于Lua脚本原子性和阻塞性的两条线索:Redis保证一段Lua脚本从开始到结束是不可分割的原子操作,代价就是脚本内部无论在做什么——是Lua自己的计算循环,还是委托给redis.call执行的一条Redis原生命令——都占据着同一个单线程事件循环,其它客户端的请求都要排在后面等。这也是为什么"脚本本身很短"不等于"脚本执行很快":决定脚本会不会拖慢整个Redis实例的,从来不是脚本的代码行数或者写没写循环,而是脚本内部调用的每一条Redis命令,其时间复杂度是不是跟数据规模挂钩——一条SORT、KEYS、SMEMBERS这类O(N)甚至O(NlogN)的命令,只要背后的数据量够大,哪怕只调用一次,一样能把整个实例卡住几秒钟,效果跟一个失控的死循环没有本质区别。

分步演示:一条SORT怎么拖慢整个实例

每一步都是本文上面用200万元素的SORT跑出来的原始结果。

慢的不是Lua,是脚本里调用的那条命令本身
这一步新验证的场景

    本文用到的工具

    • 本机 Redis 8.10.1,200万元素的list
    • SORT 通过 EVAL 调用:验证脚本内部调用O(N)命令对并发请求的阻塞效果
    京ICP备2026067502号-1京公网安备11010502063487号