redis lua脚本禁用print等api,调试需用redis.call("set"/"publish")输出信息;cjson.encode用于安全序列化简单结构;exists比get更安全;必须用argv或exists动态开关调试逻辑。

Redis Lua脚本里根本不能用 print 或 log
Redis 的 Lua 环境被严格沙箱化,print、io.write、os.time 全部被禁用,任何尝试调用都会触发 Lua script attempted to access a forbidden API 错误。这不是配置问题,是 Redis 内核硬限制——日志只能从外部观察,不能在脚本里“输出”。
可行的替代方案只有一个:用 redis.call("publish", ...) 或 redis.call("set", ...) 把调试信息“写出去”,再由外部监听或查键值。
- 最常用的是
redis.call("set", "debug:trace:" .. ARGV[1], tostring(tonumber(ARGV[2]) + 1)),把中间状态存成临时 key - 如果客户端支持 Pub/Sub(比如 Python 的
redis-py),可用redis.call("publish", "lua:debug", "step_2: " .. cjson.encode(data)),再起一个订阅线程收消息 - 避免用
redis.call("lpush", "debug:log", ...)长期积累——Lua 脚本执行期间无法保证原子性回滚,残留日志会污染数据
为什么 cjson.encode 是调试必备,但别 encode 太深
Redis 内置了 cjson 库,能安全把 table、number、string 转成 JSON 字符串,方便外部程序解析。不 encode 直接拼接字符串,遇到 nil、function、userdata 会直接报 bad argument #1 to 'concat' (table expected, got nil)。
- 只 encode 确认结构简单的变量,比如
cjson.encode({key = KEYS[1], count = #KEYS}) - 别对整个
redis.call("hgetall", "user:123")返回结果直接 encode——它返回的是交替 key/value 的 flat table,JSON 化后难读且易超长 - 超过 1KB 的 JSON 字符串可能触发
ERR Error running script (call to f_...): @user_script:xx: user_script:xx: too long string,Redis 对单个返回值有隐式长度限制(通常 512MB 以内,但实际受 client output buffer 控制)
调试时 redis.call("exists", ...) 比 redis.call("get", ...) 更安全
想确认某个中间 key 是否生成成功?别急着 get,先 exists。因为 get 对不存在的 key 返回 false,而 Lua 里 false 和 nil 行为不一致,容易引发后续 attempt to concatenate a nil value。
-
local v = redis.call("get", "tmp:val"); if not v then return "missing" end—— 这里v是false,不是nil,但很多开发者误当nil处理 - 更稳妥写法:
if redis.call("exists", "tmp:val") == 1 then local v = redis.call("get", "tmp:val") ... end - 注意:
exists在 Redis 6.0+ 返回整数 1/0,在旧版返回 1/nil,所以判断必须用== 1,不能用if redis.call("exists", ...)
线上禁用调试逻辑的两个硬开关
留着 set debug:xxx 或 publish 在生产脚本里,轻则拖慢 QPS,重则压爆 Pub/Sub channel 或填满内存。必须显式关闭,不能靠“注释掉”了事——Lua 注释不影响字节码加载,且上线前容易漏。
- 加一层条件开关:用
if tonumber(ARGV[1]) == 1 then redis.call("set", ...) end,调试时传ARGV[1] = 1,否则传0 - 更彻底的做法:用
redis.call("exists", "lua:debug:enabled") == 1动态控制,运维可随时set lua:debug:enabled 0全局关闭 - 千万别用
if DEBUG then ... end——Lua 没有全局DEBUG变量,这行会直接报attempt to compare a nil value with number
真正麻烦的不是写不出调试逻辑,而是删不干净、关不彻底。哪怕只多存一个 debug:step key,下次扩容或故障复盘时,它就可能混在 key 统计里,误导容量评估。










