Predis 中 evalSha 需手动 scriptLoad 预加载脚本并缓存 SHA,否则报 ERR NOSCRIPT;eval 不自动 fallback;Cluster 要求 KEYS 同 slot;ARGV 传参需避免 null 等非标量类型;Lua 脚本须精简防阻塞。

为什么 eval 和 evalSha 在 Predis 中行为不一致?
因为 Predis 默认不会自动缓存 Lua 脚本 SHA1 值,调用 evalSha 前必须确保脚本已用 scriptLoad 预加载,否则会报错 ERR NOSCRIPT No matching script. Please use EVAL。这不是 Redis 问题,而是 Predis 没帮你做“自动注册”。
实操建议:
- 生产环境务必优先用
evalSha+scriptLoad组合,避免每次传输完整脚本(尤其 >1KB 时网络开销明显) - 不要依赖 Predis 的
eval自动 fallback 到evalSha—— 它不会自动做 SHA 缓存 - 脚本内容有变更时,SHA1 必然变化,需重新
scriptLoad,不能复用旧 SHA
如何安全地预加载并复用 Lua 脚本 SHA?
手动管理 SHA 是最可控的方式。Predis 不提供全局脚本注册表,所以得自己缓存 SHA 值(比如存在本地静态变量或 APCu 中)。
示例:一个带参数校验的原子计数器脚本
立即学习“PHP免费学习笔记(深入)”;
$lua = "if redis.call('exists', KEYS[1]) == 1 then return redis.call('incrby', KEYS[1], ARGV[1]) else return 0 end";
$sha = $client->script('load', $lua); // 返回 SHA1 字符串,如 "a1b2c3..."
// 后续直接执行
$result = $client->evalSha($sha, ['my_counter'], [5]);
注意点:
-
scriptLoad是幂等操作,重复调用返回相同 SHA,可放心在启动时集中加载 - 若用 APCu 缓存 SHA,记得加前缀防冲突,例如
apcu_store('redis:sha:counter_incr', $sha) - Redis Cluster 模式下,所有
KEYS必须落在同一 slot,否则evalSha会抛CROSSSLOT Keys in request don't hash to the same slot
传参时 KEY 和 ARGV 怎么对应?常见类型陷阱有哪些?
Predis 将 PHP 数组按顺序分别映射为 Redis 的 KEYS 和 ARGV,但类型会被强制转为字符串 —— 这是多数逻辑错误的根源。
例如这段 Lua:
return tonumber(ARGV[1]) + tonumber(ARGV[2])
如果 PHP 传入 [1.5, '2'],Lua 中 ARGV[1] 是字符串 "1.5",tonumber() 可解析;但若传 [null, 2],ARGV[1] 变成 " "(空格字符串),tonumber(" ") === nil,脚本直接报错。
安全做法:
- PHP 端确保
ARGV全为标量(int/float/string),避免null、array、object - Lua 端对关键参数做防御性检查:
if not tonumber(ARGV[1]) then return error("ARGV[1] must be number") end - KEYS 数组长度必须 ≥1,且所有 key 必须真实存在(否则
exists返回 0),不能传空字符串或null
并发高时 Lua 脚本执行慢,怎么定位和优化?
Redis 是单线程执行 Lua,脚本里任何耗时操作(如循环上万次、嵌套 redis.call 超过 5 次)都会阻塞整个实例。
排查与优化方向:
- 用
redis-cli --latency观察延迟毛刺,再结合SLOWLOG GET 5查看是否命中慢脚本 - 避免在 Lua 中做 JSON 解析、正则匹配、大数组遍历 —— 这些该由 PHP 层处理
- 把“读-改-写”拆成纯命令组合(如用
INCRBY替代EVAL "get+set+incr")更高效 - 脚本超过 50 行或含条件分支嵌套 >3 层,建议重构为多个小脚本 + PHP 协调
真正难的不是写对脚本,而是让脚本足够短、足够确定、足够无状态 —— 否则它就成了 Redis 的定时炸弹。



















