Redis Lua脚本仅支持尾递归,因其Lua 5.1解释器可复用栈帧避免深度增长;普通递归每次调用均压栈,易触达默认约10000层的硬性栈限制而报错,且无法调整栈大小,故必须用迭代或严格守卫的尾递归。

Redis Lua 脚本支持递归,但有硬性限制
支持,但必须是尾递归(proper tail recursion),且深度受 Redis 内置栈限制约束。非尾递归或过深的调用会触发 ERR Error running script (Lua traceback) 错误,甚至导致整个脚本中止。
为什么尾递归能跑,普通递归不行
Redis 使用的 Lua 5.1 解释器支持尾递归优化,即当函数调用是整个表达式的最后一个动作时,解释器会复用当前栈帧,不增加调用深度。但普通递归(如 fib(n-1) + fib(n-2))会在每次调用都压栈,很快撞到 Redis 的默认栈深度上限(通常为 10000 层,实际可用远低于此,因脚本本身也占栈空间)。
-
redis.call和redis.pcall每次调用也会消耗栈帧,叠加后更容易溢出 - Redis 不提供调整栈大小的配置项,无法绕过该限制
- 日志里看不到详细栈跟踪,只有模糊的
Lua traceback提示,调试困难
怎么写安全的递归逻辑
优先改用迭代替代递归;若必须递归,只用尾递归形式,并严格控制参数范围。例如实现“逐层遍历嵌套列表”时:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
local function walk_list(t, depth)
if depth > 100 then
return { err = "max depth exceeded" }
end
if type(t) ~= "table" or #t == 0 then
return t
end
-- 尾递归:最后一步是调用自身,无后续计算
return walk_list(t[1], depth + 1)
end- 显式传入
depth并做守卫判断,避免无限递归 - 避免在递归调用后拼接字符串、做加法等操作,否则就不是尾递归
-
KEYS和ARGV中的数据结构深度也要纳入预估,不能只看代码层数
容易被忽略的隐性递归风险
看似没写递归,但某些标准库函数内部可能触发——比如 table.sort 在比较函数里调用自身、或 string.gsub 的替换函数反复触发。更隐蔽的是:用 redis.call 修改 key 后又在同个脚本里读取它,若业务逻辑形成闭环(如计数器自增后判断阈值再重试),也会等效于递归调用。
这类逻辑不会报栈溢出,但会导致脚本超时或阻塞 Redis 单线程,比栈溢出更难定位。

















