redis.log() 是 Redis Lua 脚本中唯一可用的调试输出方法,只支持两个参数(日志级别与已拼接好的字符串),不支持 print 或格式化输出;调试时需要用 tostring() 显式转换变量,并避免与 nil 直接拼接,同时日志级别还要与 redis.conf 中的配置保持一致。

Redis Lua脚本里 redis.log() 是唯一可用的调试输出方式
在 Redis Lua 脚本调试过程中,Redis 并不支持 print()、io.write() 以及标准 Lua 的 I/O 输出函数,因此所有调试信息都必须通过 redis.log() 输出。这个函数只接收两个参数:level(例如 redis.LOG_DEBUG)和 message(必须是字符串,不支持额外格式化参数,也不能直接传多个变量)。
最常见的错误写法之一,是直接写成 redis.log(redis.LOG_DEBUG, "count:", count),这种写法会触发 ERR Error running script (call to f_...): user_script:1: wrong number of arguments。根本原因在于 redis.log() 不是多参数输出函数,它只能接收一条完整字符串消息。
- 正确写法:使用
..拼接字符串,例如redis.log(redis.LOG_DEBUG, "count=" .. tostring(count)) - 注意
tostring()需要显式调用,尤其是在处理nil、table等类型时非常关键;对于table,可先用cjson.encode()转成字符串(需提前通过redis.call("eval", "return require('cjson')", {})确认模块可用) - 日志级别建议优先从
redis.LOG_WARNING开始使用,避免大量调试日志淹没 Redis 日志文件;上线后记得删除或注释掉不必要的redis.log调用
本地模拟执行时,redis.call() 和 redis.pcall() 行为与真实环境不一致
如果直接在本地使用纯 Lua 解释器(例如 lua 命令)运行 Redis 脚本,通常会失败,因为 redis.call() 必须依赖 Redis 服务端上下文。更稳妥的调试方式,是使用 redis-cli --eval 在开发环境直接连接 Redis 进行测试,这样更接近真实执行场景,也更适合定位 Redis Lua 脚本逻辑错误。
另一个典型坑点,是误解 redis.pcall() 的返回结构,捕获异常后却没有正确处理结果。它始终返回一个两元素 table,其中 ok 字段为 boolean,第二个位置要么是错误信息,要么是执行结果。如果直接读取 res[2],却不先判断 res[1],就很容易造成逻辑判断失误。
- 推荐写法:
local res = redis.pcall("get", "key"); if not res[1] then return res[2] end; local val = res[2] - 不要在脚本中调用未启用的命令(例如
redis.call("bf.exists")),否则会直接报ERR unknown command,并且不会进入pcall的 error 分支——而是立刻中断整个脚本 redis.call()一旦抛出异常,会终止整段 Lua 脚本;redis.pcall()虽然不会直接中断,但必须手动校验返回值,否则后续逻辑可能在nil或错误结果基础上继续执行
脚本超时或原子性破坏常因循环 + 多 key 操作引发
Redis 执行 Lua 脚本时具备单线程和原子性特征,但如果脚本耗时过长,就会阻塞其他请求,影响整体性能。常见低效写法是在 for 循环里反复执行 redis.call("get", key),逐个查询大量 key,而不是改成一次性使用 redis.call("mget", ...) 批量获取。
另一个容易被忽视的问题,是脚本中混合读写操作时没有保证 key 使用方式一致。比如使用 KEYS[1] 写入,却把 ARGV[1] 当作 key 名称去读取,这会违反 Redis 集群下的 key hash tag 规则,在集群模式中可能直接报出 ERR hash slot not covered。
- 务必保证所有涉及的 key 都显式声明在
KEYS数组中,并且脚本内部只通过KEYS[i]访问这些 key - 尽量避免在循环中频繁调用
redis.call();优先采用批量命令,如mget、mset、evalsha预加载等方式优化执行效率 - 判断 key 是否存在时,优先使用
redis.call("exists", KEYS[1]) == 1,不要写成redis.call("get", KEYS[1]) ~= nil——前者性能更好,也能避免空字符串场景带来的干扰
调试时最容易被忽略的是客户端传参类型和编码问题
Redis 协议会把所有 ARGV 参数都按字符串传入 Lua 脚本。也就是说,即使你使用 redis-cli --eval script.lua key1 key2 -- 123 true,脚本中拿到的 ARGV[1] 依然是 "123",ARGV[2] 依然是 "true",它们并不是 number 或 boolean 类型。
因此,Redis Lua 脚本调试中很常见的一类错误,就是直接写 if ARGV[1] == 1 then,结果条件永远为 false;或者调用 tonumber(ARGV[1]) 后,没有处理返回 nil 的情况,最终导致后续判断或计算出现异常。
- 显式转换并校验:
local limit = tonumber(ARGV[1]); if not limit or limit <= 0 then return redis.error_reply("invalid limit") end - 布尔值建议统一约定为字符串形式,例如
"1"/"0"或"true"/"false",然后通过ARGV[2] == "1"这类方式判断 - 如果需要传递 JSON 结构参数,客户端应先执行
json.encode(),脚本中再使用cjson.decode(ARGV[n])解析——但要注意cjson在部分 Redis 旧版本(如 <4.0)中可能不可用
很多 Redis Lua 脚本的逻辑错误,并不在算法本身,而是隐藏在类型转换、参数编码以及 KEYS/ARGV 的边界处理上。每次修改脚本后,建议用最小可复现场景配合 redis.log() 逐步打点排查,这通常比反复猜测问题来源更快、更准确。
