EVAL脚本会突然触发noeviction拒绝写入,是因为Redis在执行前预检内存水位,used_memory超maxmemory即硬性返回OOM错误,不解析也不执行脚本;其易踩阈值主因是LRANGE加载大列表、Lua中构建大table及并发EVAL推高used_memory_peak。

为什么EVAL脚本会突然触发noeviction拒绝写入?
因为Redis在执行EVAL前会做一次内存水位预检——不是等脚本跑完才发现OOM,而是在进入Lua执行器之前就查used_memory是否已超maxmemory。一旦超标且淘汰策略是noeviction(默认值),直接返回(error) OOM command not allowed when used memory > 'maxmemory',连脚本解析都不让开始。
EVAL比普通命令更容易踩中内存阈值的三个原因
脚本本身不占大内存,但它的执行行为会集中放大内存压力:
- 脚本里调用
redis.call('LRANGE', 'biglist', 0, -1)会把整个列表加载进Lua堆,瞬间吃掉几MB甚至上百MB内存 - 循环拼接字符串或构建大table(如
local t = {}; for i=1,10000 do table.insert(t, 'item'..i) end)会在Lua状态机内部分配大量临时对象 - 多个并发
EVAL请求排队等待单线程执行,导致used_memory_peak被瞬时拉高,而used_memory还没来得及回收上一轮的中间结果
SCRIPT LOAD和EVALSHA并不能绕过这个检查
很多人误以为改用EVALSHA就能避开内存问题,其实不然:
-
SCRIPT LOAD只是缓存字节码,不执行,不触内存检查 -
EVALSHA执行时仍要新建Lua状态机、加载KEYS/ARGV、分配运行时变量——该占的内存一点不少 - 哪怕脚本只做
redis.call('INCR', KEYS[1]),只要此时Redis内存已到临界点,照样报OOM
怎么快速判断是不是脚本引发的内存卡点?
别只盯着脚本内容,先看Redis实时内存指标:
- 执行
INFO memory,重点看used_memory_peak是否远高于当前used_memory——说明有未释放的峰值残留 - 检查
mem_fragmentation_ratio是否>2.0——高碎片会让可用内存虚高,实际分配失败更频繁 - 对比
evicted_keys和expired_keys增长速度:如果evicted_keys突增,说明淘汰策略已在生效,脚本很可能就是压垮骆驼的最后一根稻草
真正棘手的地方在于:这个OOM不是Lua报的错,而是Redis主线程在入口拦截的硬性拒绝。你没法在脚本里catch,也没法靠重试缓解——必须从内存水位、脚本数据规模、并发节奏三处同时收口。


















