根本解法是切断“脚本内存暴涨→触发淘汰失效→写入被拒”链路;lua-time-limit仅限执行时长,无法限制内存;必须显式配置maxmemory-policy为noeviction,并禁用hgetall等全量命令,客户端预检memory usage。

单纯调大 maxmemory 或只设 lua-time-limit 无法阻止 Lua 脚本触发 OOM command not allowed when used memory > 'maxmemory' 报错。根本解法是切断“脚本内存暴涨 → 触发淘汰失效 → 写入被拒”这条链路。
为什么 lua-time-limit 对 OOM 完全无效
lua-time-limit 控制的是执行时长上限(毫秒),不是内存用量。一个脚本完全可以在 100ms 内完成以下操作:
- 用
redis.call("HGETALL", "huge_hash")一次性读出 50MB 数据 - 用
table.insert循环构造 20 万个字符串项 - 执行完前,
used_memory已突破maxmemory
此时 Redis 不会等超时,而是立刻拒绝后续写入——因为内存已满,且脚本执行期间淘汰策略不生效。
必须显式启用 noeviction 策略并验证拼写
Redis 默认行为在不同版本中不一致,且配置项大小写/空格敏感。仅写 maxmemory 2gb 而不配策略,等于没设防。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 执行
redis-cli CONFIG GET maxmemory-policy,输出必须是noeviction(全小写、无空格、无引号) - 若返回
volatile-lru或空值,说明策略未生效,maxmemory形同虚设 - 配置文件中必须写成两行(顺序无关):
maxmemory 2gb<br>maxmemory-policy noeviction
- 改完后运行
redis-cli CONFIG REWRITE落盘,重启后立即验证
脚本内必须规避三类内存放大操作
OOM 报错的直接诱因几乎都来自脚本内部逻辑失控,而非外部数据量本身。
- 禁用
HGETALL、LRANGE key 0 -1、SMEMBERS等全量命令;改用客户端分页 +HSCAN/SSCAN,Lua 中不承担“拉全量再过滤”职责 - 避免字符串拼接累积:
res = res .. item改为table.insert(buf, item)+ 最终table.concat(buf) - 循环必须带硬上限:
for i = 1, math.min(#KEYS, 500) do,不能依赖#KEYS或ARGV长度不做截断 - 大 table 初始化用
table.new(n, 0)(Lua 5.2+),减少动态扩容抖动
客户端要预检 key 大小并熔断
Redis 不会在脚本执行前做内存预估,这事得由客户端兜底。
- 调用脚本前,先执行
MEMORY USAGE key_name,若返回值 > 1MB,直接拒绝执行 - 对批量操作(如传入多个 key),逐个查
MEMORY USAGE并累加,总和超阈值则 abort - Spring Boot 中使用
RedisTemplate时,应在 service 层封装该检查逻辑,不能依赖 Lua 自查 - 注意:从节点默认只读,
MEMORY USAGE在 replica 上不可用,必须路由到 master 执行
真正危险的不是脚本慢,而是它在几毫秒内把内存打穿——这种瞬间冲击连 maxmemory-policy 都来不及响应。所有防护必须落在“脚本不许干这事”和“客户端不许让它干这事”两个层面,缺一不可。










