直接用Eval易出错,因keys必须显式传入否则集群报错,且每次全量传输脚本开销大;应优先ScriptLoad预加载+EvalSha执行,并处理redis.Nil回退。
为什么直接用 Eval 容易出错
go 里用 go-redis 调 lua 脚本,第一反应是调 eval 方法,但多数人没意识到:脚本里用到的 key 必须显式传入 keys 参数,redis 才允许在集群模式下路由;漏传会报 err wrong number of arguments for 'eval' command 或更隐蔽的 moved 错误。另外,eval 不校验脚本 sha,每次都是全量传输,网络和解析开销大。
EvalSha 配合 ScriptLoad 是更稳的选择
先用 ScriptLoad 把脚本预加载到 Redis,拿到 SHA1 值,后续全用 EvalSha 执行——既避免重复传输,又绕过集群 key 路由限制(只要 keys 正确)。注意两点:
-
ScriptLoad只需执行一次(比如服务启动时),失败要重试或 panic,不能每次请求都调 -
EvalSha返回redis.Nil表示脚本未加载,这时得 fallback 到Eval或触发重载 - SHA 值是字符串,必须用
fmt.Sprintf("%x", sha)转成小写十六进制,否则EvalSha会返回错误
示例片段:
sha, err := rdb.ScriptLoad(ctx, script).Result()
if err != nil {
panic(err)
}
result, err := rdb.EvalSha(ctx, fmt.Sprintf("%x", sha), keys, args...).Result()
key 和 arg 的传参顺序不能颠倒
Eval 和 EvalSha 的参数签名是 (ctx, script, keys, args... interface{}),其中 keys 是切片(哪怕只有一个 key 也要传 []string{"mykey"}),args 是变参。常见翻车点:
- 把 key 当作 arg 传,导致脚本里
KEYS[1]取不到值,返回 nil - 用
append([]string{}, key)拼keys,但忘了keys类型必须是[]interface{},得转成[]interface{}{key} - 脚本里用了
ARGV[1],但 Go 侧漏传args,结果取到空字符串而非预期值
原子性只在单个 Redis 实例/槽位内成立
Lua 脚本的原子性不是“全局事务”,而是“单次命令执行不被中断”。这意味着:
- 如果操作涉及多个 key,它们必须落在同一个 Redis 槽(cluster 模式下要求所有 key 有相同哈希标签,比如
{user1001}:balance和{user1001}:lock) - 脚本里不能调用阻塞命令(如
BLPOP),否则整个 Redis 线程卡住 - 超长脚本(>5s)会被 Redis 中断并报
BUSY Redis is busy running a script,得加SCRIPT KILL或重启
真正难的是设计阶段:你得提前确认所有 key 是否天然聚类,而不是等上线后才发现 MOVED 错误频发。


















