redis lua脚本必须短、快、确定、无副作用;须用local声明所有变量;禁用math.random();redis.call()与redis.pcall()需按场景严格区分;脚本执行时间应控制在5ms内;keys数量须与eval命令numkeys参数严格一致。

生产环境里写 Redis Lua 脚本,核心就一条:**脚本必须短、快、确定、无副作用**。不是“最好别超长”,而是“超了就可能拖垮整个 Redis 实例”。
脚本必须用 local 显式声明所有变量
Lua 在 Redis 沙箱中禁用全局变量污染——但不报错,只静默失败或行为异常。比如漏写 local 的 counter,第二次执行时可能读到上一次残留值。
- 错误写法:
counter = redis.call('GET', KEYS[1])(变成全局变量) - 正确写法:
local counter = redis.call('GET', KEYS[1]) - 所有循环变量、中间结果、函数参数都得加
local,否则在高并发下极易引发状态泄露
禁止使用 math.random(),改用 redis.sha1hex() 或传参
Redis 重写了 math.random() 为确定性伪随机(每次调用返回相同序列),这在限流、分片等依赖真随机的场景会出问题。
- 不要依赖
math.random(1,100)做抖动或采样 - 如需唯一标识,可用
redis.sha1hex(ARGV[1] .. os.time())(注意os.time()是允许的) - 更稳妥的方式是让客户端生成随机数,通过
ARGV传入
redis.call() 和 redis.pcall() 必须按场景选,不能混用
redis.call() 遇错直接中断脚本并抛异常;redis.pcall() 捕获错误返回 {err="..."} 表,脚本继续执行。生产脚本里选错会导致原子性失效或静默失败。
- 对关键键操作(如扣库存、删锁),用
redis.call()—— 错了就该失败,不掩盖问题 - 对非关键查询(如尝试读一个可能不存在的配置),用
redis.pcall()避免整个脚本崩掉 - 绝不能在一个脚本里对同一个键先
pcall再call,容易因状态不一致引发逻辑漏洞
脚本长度和执行时间必须受控,超 5ms 就要警惕
Redis 单线程执行 Lua,脚本运行期间所有其他命令排队等待。实测一个含 1000 次 redis.call('INCR') 的循环,在普通机器上就可能卡住 20ms+,触发慢日志甚至客户端超时。
- 单个脚本逻辑行数建议 ≤ 50 行(不含空行和注释)
- 避免
for i=1, #KEYS do ... end处理大量 key —— 改用服务端批量命令(如HMGET)或拆成多个小脚本 - 上线前务必用
redis-cli --latency+EVAL测真实耗时,别信本地模拟
最容易被忽略的一点:**脚本里的 KEYS 数量必须和 EVAL 命令传入的 numkeys 严格一致**。少传一个 key,KEYS[1] 可能是 nil;多传一个,多余 key 不进数组但会占用参数位——这种错不会报语法错误,只会在特定数据条件下偶然失败,极难复现。











