在 Redis 低版本(如 6.2 及更早版本)中,编写和执行 Lua 脚本必须遵循一系列兼容性规则。比如,脚本必须使用 Lua 5.1 语法,像 goto(仅限标签跳转)、table.unpack 这类较新的语言特性都不能直接使用。同时,所有变量都应声明为 local,KEYS/ARGV 也必须显式传参。另外,异常处理通常依赖 redis.pcall,还要重点避免脚本超时以及集群环境下的跨 slot 访问问题。

在 Redis 的低版本环境中(例如 Redis 6.2 及更早版本),无法使用 Redis Functions。这时只能通过 EVAL/EVALSHA 配合原生的 Lua 5.1 运行环境来实现脚本逻辑。实际开发中,这类 Redis Lua 脚本兼容性问题并不少见,主要集中在语言特性差异、命令 API 限制以及运行时约束上。也就是说,不是脚本语法写对了就一定能执行,而是必须按照低版本 Redis 的规则来编写,才能稳定运行。
用 Lua 5.1 语法编写,避免使用 5.2+ 新特性
Redis 内置的 Lua 解释器固定为 Lua 5.1(截至 Redis 7.2 依然没有变化),因此 goto、table.unpack(需要手动展开)、utf8 模块、__gc 元方法等能力都不可直接使用。常见兼容性问题包括:
continue不存在 → 可改写为if not cond then goto skip end+::skip::(goto在 5.1 中可用,但仅支持标签跳转,不能跨越作用域)table.pack和table.unpack不可用 → 需要手动构造 table,或结合{...}与select完成拆包- 全局变量被禁止 → 所有变量都必须声明为
local,否则脚本加载时会失败,并报错ERR Error running script (call to f_...): @user_script:line: attempt to assign a global variable - 字符串模式不支持
%u或 Unicode 类匹配 → 编写匹配逻辑时应尽量避免使用非 ASCII 字符类
KEYS 和 ARGV 必须显式传入,不能写死键名
在 Redis Lua 脚本中,所有键名都必须通过 KEYS[1]、KEYS[2] 等方式访问,参数值则通过 ARGV[1] 等传入;如果把键名直接硬编码到脚本里(例如 "user:1001"),会导致脚本难以复用,也可能影响缓存命中,使调用频繁退化为 EVAL 而不是 EVALSHA,增加带宽消耗并降低执行效率。
正确写法示例:
return redis.call('incrby', KEYS[1], ARGV[1])
错误写法(不可缓存、不安全、违反 Redis 集群 key hash tag 规则):
return redis.call('incrby', 'counter', ARGV[1])
还需要注意:numkeys 参数必须与 KEYS 数组长度严格一致,传多或传少都会触发 ERR Error running script (call to f_...): @user_script: line: wrong number of arguments 错误。
错误处理必须使用 redis.pcall,不要只依赖 redis.call
redis.call 一旦抛出异常,会立即中断整个 Lua 脚本并把错误返回给客户端;而 redis.pcall 会返回类似 {ok=true, result=...} 或 {ok=false, err="..."} 的结果结构,便于开发者根据返回值进行分支处理。在低版本 Redis 中没有 try/catch 机制,因此这基本是最可控、最实用的异常处理方式。
典型用法:
local res = redis.pcall('hget', KEYS[1], ARGV[1])
if not res.ok then
return {err = res.err}
end
return res.result
尤其要注意:redis.pcall 对 PUBLISH、SCRIPT KILL 等命令同样会返回表结构,但某些命令(例如 EXPIRE 在 key 不存在时)返回的是 0(Lua number),而不是 error,因此判断结果时不能只看 ok 字段,还需要结合具体业务逻辑分析。
避免超时和阻塞,尤其是 Redis 6.2 之前的环境
低版本 Redis(< 7.0)不支持 EVAL_RO 或 FCALL_RO,因此所有脚本默认都具备读写能力;同时,lua-time-limit 默认是 5 秒,且不能随意降得太低(CONFIG SET lua-time-limit 最小支持 100ms)。一旦 Lua 脚本执行过慢或卡死,整个 Redis 单线程都会被阻塞,影响线上服务。
- 不要在循环中频繁调用
redis.call(例如 for i=1,1000 do redis.call(...) end),虽然省去了网络往返,但 CPU 执行时间很容易超出限制 - 尽量避免递归、深层嵌套 table 查找和大数组遍历 —— Lua 5.1 的 GC 机制与 table 性能明显弱于更新版本
- 在 Redis Cluster 环境下,所有
KEYS必须位于同一个 slot,否则会直接报错CROSSSLOT Keys in request don't hash to the same slot,没有自动兜底机制
还有一个经常被忽视的细节是:脚本中调用 redis.log 虽然不会直接影响执行流程,但在低版本 Redis 中,部分日志级别(如 redis.LOG_WARNING 等)可能并不完全生效,而且日志内容不会自动截断。如果输出的字符串过长,可能撑满日志缓冲区,甚至引发脚本静默失败。
