noscript 和 busyscript 错误均源于 lua 脚本生命周期管理:前者因 sha1 未预热或未同步至集群节点,后者因单线程环境存在阻塞脚本未退出。

为什么 EVAL 执行 Lua 脚本会报 “NOSCRIPT” 或 “BUSYSCRIPT”?
这两个错误本质都指向脚本生命周期管理问题。NOSCRIPT 表示 Redis 找不到你用 EVALSHA 提交的 SHA1 值对应脚本,常见于脚本未预热、集群节点间未同步或客户端缓存了过期 SHA;BUSYSCRIPT 则说明有另一个脚本正在执行且未退出(比如死循环或阻塞 I/O),而 Redis 的 Lua 环境是单线程、无抢占的。
实操建议:
- 永远优先用
EVAL开发调试,上线前再切到EVALSHA+SCRIPT LOAD流程,避免手动生成 SHA 出错 - 在 Redis 集群中,
SCRIPT LOAD只作用于当前节点,必须确保所有涉及的 slot 所在节点都执行过该命令(或改用客户端自动分发) - 用
redis.call("time")或简单计数器做超时防护,别依赖外部时间源——Lua 环境里没有os.time() - 禁用
redis.log()在生产环境输出大量日志,它会拖慢脚本且不落盘,排查时反而干扰判断
为什么脚本里用 KEYS[1] 但实际操作了非传入 key 的数据?
Redis 强制要求所有被 Lua 脚本读写的 key 必须显式声明在 KEYS 数组里,否则会直接拒绝执行(ERR script tried to access a non existent key)。这不是语法检查,而是运行时安全策略——目的是让 Redis 预判 key 分布,支撑集群路由和事务隔离。
实操建议:
- 不要在脚本里拼接 key 名再用
redis.call("GET", "prefix:"..arg[1]),这会绕过校验,集群下大概率路由失败或报错 - 如果逻辑上确实需要动态 key(如分页扫描),改用
SCAN+ 客户端过滤,或把所有可能涉及的 key 全部传入KEYS(需控制数量,避免过大) -
ARGV只能传值,不能当 key 用;想把参数当 key 处理?不行——必须挪到KEYS里,哪怕只用一次 - 使用
redis.call("exists", KEYS[1])检查 key 存在性是安全的,但redis.pcall不会绕过 key 声明规则
为什么本地测试没问题,上生产却出现 “OOM command not allowed when used memory > 'maxmemory'”?
Lua 脚本执行期间,Redis 不会触发内存淘汰(eviction),也不会响应 CLIENT KILL 或 SHUTDOWN。一旦脚本生成大量中间数据(比如用 table.insert 累积几千个结果再 return),就可能瞬间冲破 maxmemory 限制,导致整个实例拒绝写入。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
实操建议:
- 避免在 Lua 中构建大 table:不用
for i=1,10000 do table.insert(res, redis.call("GET", KEYS[i])) end,改用多次小批量EVAL或服务端游标(如SCAN) - 用
redis.call("hlen", KEYS[1])替代遍历HGETALL再数长度;用EXISTS替代GET后判空——减少返回数据量 - 注意
redis.call("mget", unpack(KEYS))的 unpack 会复制全部 key 引用,KEYS 超过几百个时开销明显,不如分批 - 监控指标重点看
lua_calls和instantaneous_ops_per_sec的突增,往往比内存告警更早暴露问题
如何安全地实现带条件的原子更新(比如“库存 > 0 才扣减”)?
这是最常被误写的场景。很多人直接写 if tonumber(redis.call("GET", KEYS[1])) > 0 then redis.call("DECR", KEYS[1]) end,看似原子,但忽略了两个关键点:一是 GET 和 DECR 之间存在竞态窗口(虽然极短,但 Lua 执行不是真正“不可中断”的硬件原子),二是没处理返回值,调用方无法知道是否真的扣成功了。
实操建议:
- 必须用
redis.call("decrby", KEYS[1], 1)获取扣减后值,再判断是否 ≥ 0;或者用redis.call("getset", KEYS[1], new_val)配合业务逻辑重算 - 永远
return明确状态,比如return { ok = true, left = new_stock },别让客户端靠异常与否来判断成败 - 不要在脚本里做复杂计算后再决定是否执行命令——把决策逻辑尽量压到 Redis 原生命令里(如
INCRBY配合负数检测) - 如果条件依赖多个 key 的组合状态(如“用户余额够且商品有库存”),务必把所有 key 放进
KEYS,并确认它们落在同一分片(集群模式下)
最易被忽略的一点:Lua 脚本的“原子性”仅限于单次 EVAL 执行过程,不提供跨命令、跨请求、跨实例的事务语义。任何试图在脚本里模拟分布式锁、重试逻辑或 fallback 行为,都会迅速滑向不可维护的边缘。










