Redis脚本需定期用SCRIPT EXISTS检查存在性,避免EVALSHA报错;禁用全局SCRIPT FLUSH,应按业务维度管理哈希,配合外部元数据存储与TTL淘汰。

SCRIPT EXISTS 要定期查,别等 EVALSHA 报错才想起它
Redis 不会自动清理已加载但长期不用的脚本,SCRIPT LOAD 生成的 SHA1 会一直留在内存里,直到手动 SCRIPT FLUSH 或重启。但更现实的问题是:你存了 200 个脚本哈希,其中 80% 只在灰度期用过一次,后续全靠 fallback 到 EVAL——这说明脚本管理已经失控。
实操建议:
- 上线新脚本前,先用
SCRIPT EXISTS检查 SHA1 是否已存在,避免重复LOAD - 每日定时任务调用
SCRIPT EXISTS批量验证核心脚本(比如支付、库存类),返回0就触发告警 + 自动重载 - 别依赖客户端缓存 SHA1 —— 集群节点可能因故障丢失脚本缓存,
EVALSHA直接返回(error) NOSCRIPT No matching script. Please use EVAL.
PhpRedis 的 evalSha() 自动 fallback 不可靠
官方文档说“先试 EVALSHA,失败再 EVAL”,但 PhpRedis 的实际行为取决于版本和集群模式。在 Redis Cluster 下,evalSha() 可能因路由到错误节点而静默失败,且 getLastError() 不一定及时捕获;更糟的是,某些旧版扩展在 fallback 时没重传 KEYS 和 ARGV 结构,导致 EVAL 执行参数错位。
实操建议:
- 永远显式判断
$redis->evalSha($sha, $keys, $args)返回值是否为false,再检查$redis->getLastError()是否含NOSCRIPT - fallback 时务必用完整脚本内容重试,不要尝试“修复” SHA1 或拼接参数
- 生产环境禁用
redis.session.lock_retries类似机制——它内部也用EVALSHA,但错误处理路径和业务代码不一致
批量清理不能只靠 SCRIPT FLUSH
SCRIPT FLUSH 是全局清空,相当于把所有脚本哈希“一键归零”。线上服务一旦有多个模块共用一个 Redis 实例(比如用户中心 + 订单系统 + 活动平台),贸然执行会导致其他模块的 EVALSHA 全量降级,QPS 瞬间翻倍,网络带宽打满。
实操建议:
- 按业务维度命名脚本,比如在
SCRIPT LOAD前加注释:-- user:profile:update-v2,后续用redis-cli --scan --pattern "*user:profile*" | xargs -I{} redis-cli SCRIPT KILL这类方式辅助定位(注意:Redis 本身不支持按前缀查脚本,需外部记录) - 把脚本哈希存进 Redis 的
hash结构(如lua_scripts:meta),字段为sha1 → {name, version, last_used},配合 TTL 定期淘汰 - 真正要删时,先用
SCRIPT KILL终止可能正在运行的实例,再SCRIPT FLUSH,否则残留执行中的脚本会卡住主线程
Hash 批量写入脚本别用 HMSET 循环
常见误区:写个 Lua 脚本遍历 KEYS,对每个 key 调一次 HMSET。这看似“批量”,实则仍是 N 次命令调用,原子性只保在单 key 内,且性能比客户端 for-loop 还差(Lua 解释器开销 + 多次 redis.call 调度)。
实操建议:
- 真批量写 Hash,请用
HMSET单次传入全部 field-value 对,脚本里不要循环调用redis.call - 如果必须跨多个 key 写不同 Hash,改用
pipeline+ 客户端分片,而不是塞进一个 Lua 脚本里硬扛 - 留意
lua-time-limit配置——循环体超过默认 5 秒会被SCRIPT KILL中断,且中断后状态不可回滚
脚本哈希不是“存进去就完事”的资源,它和连接池、慢日志一样,得有生命周期监控。最常被忽略的一点:Redis 的 INFO server 里 loaded_scripts 字段只报总数,不报具体 SHA1,想摸清底数,要么自己记日志,要么接受定期 SCRIPT FLUSH 后业务自愈——后者代价太高。









