redis lua脚本引发oom的根本原因是其运行时的内存副作用,如批量写入失控、递归构造大table、反复redis.call()导致临时对象堆积;需通过info memory、client list、日志分析及redis-cli --eval本地压测定位高危操作。

Redis执行Lua脚本时触发OOM错误,根本原因不是脚本本身占内存,而是脚本在运行过程中间接导致内存飙升——比如批量写入未控制数量、递归构造大table、或反复调用redis.call()产生临时对象堆积。直接查maxmemory配置或INFO memory只能看到结果,看不到脚本的“内存副作用”。
确认是否真由Lua脚本引发OOM
先排除常规内存问题,再聚焦脚本:
- 用
INFO memory检查used_memory_human和mem_allocator,确认OOM前内存是否已逼近maxmemory - 执行
CLIENT LIST,找cmd=eval或cmd=evalsha且idle值异常大的连接——说明脚本卡住或正在长耗时执行 - 查
redis.log里最近的(error) OOM command not allowed when used memory > 'maxmemory'日志,往前翻30秒,看是否紧跟着EVAL或EVALSHA命令记录 - 如果脚本是通过
SCRIPT LOAD+EVALSHA调用的,用SCRIPT EXISTS <sha1></sha1>确认脚本仍存在(避免因缓存不一致导致重复加载)
检查脚本内部的内存高危操作
Lua脚本在Redis中运行时没有GC压力释放机制,以下写法极易引发OOM:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
- 用
for i=1, 10000 do table.insert(arr, redis.call("get", KEYS[i])) end——每次redis.call()返回值都会被保留在Lua栈,大数组+大数据体直接撑爆内存 - 拼接超长字符串:
str = str .. tostring(val)循环几百次,Lua 5.1的字符串拼接会产生大量中间副本 - 未限制
SCAN游标遍历范围:脚本里用while true do local res = redis.call("scan", cursor, "count", 1000) ... break if cursor == "0" end,但没加limit计数器,遇到大key集合就无限扫 - 滥用
cjson.encode()序列化整个哈希或集合结果,尤其当value含二进制或嵌套深结构时,编码后体积可能翻数倍
用redis-cli --eval做轻量级内存行为验证
别在生产环境试错,本地快速模拟关键路径:
- 把脚本保存为
script.lua,用redis-cli --eval script.lua key1 key2 , arg1 arg2直连测试实例(确保该实例maxmemory设得小,如64mb) - 在脚本开头加
redis.log(redis.LOG_WARNING, "start, mem=" .. tostring(redis.call("info", "memory"):match("used_memory:%d+"))),观察每次调用前后内存变化 - 把疑似高开销操作替换成
redis.pcall("exists", "fake_key")占位,逐步排除——不是所有redis.call()都等价,mget比多次get省内存,hgetall比循环hget更危险 - 注意:本地用
lua命令行跑脚本会失败,因为redis.call()不存在;必须走redis-cli --eval或真实Redis上下文
真正难排查的是脚本里“看起来无害”的操作累积效应,比如一次lrange key 0 -1返回10MB列表,再table.concat转字符串,瞬间多占20MB——这些不会报语法错,但会在EVAL返回前悄悄吃光内存。上线前务必用真实数据集压测脚本的内存 footprint,而不是只测逻辑正确性。










