
本文详解 Redigo 调用 Redis Lua 脚本时常见的 “wrong number of args” 错误根源,说明 Go 层参数传递规范、Lua 脚本中 unpack() 的正确用法,并提供完整可运行的修复方案,包括参数展开、字符串解码和脚本复用优化。
本文详解 redigo 调用 redis lua 脚本时常见的 “wrong number of args” 错误根源,说明 go 层参数传递规范、lua 脚本中 `unpack()` 的正确用法,并提供完整可运行的修复方案,包括参数展开、字符串解码和脚本复用优化。
在使用 Redigo 执行 Redis Lua 脚本时,ERR Error running script ... Wrong number of args calling Redis command From Lua script 是一个高频错误。其根本原因在于:Go 层传入的 argv 参数未被正确展开为 Lua 中独立的多个参数,导致 redis.call("HMSET", KEYS[1], unpack(a)) 实际只收到 2 个参数(key + 单个 []byte),而 HMSET 至少需要偶数个参数(field-value 对)。
✅ 正确的参数传递方式
Redigo 的 Script.Do(conn, keys..., args...) 方法要求:所有 KEYS 和 ARGV 必须作为独立参数逐一传入,而非将 argv 整体作为一个切片。你原代码中:
argv := []string{"30000", "SSF_lastAccessedDate", "1481627386", "SSF_expiryDate", "2481657386"}
rScript.Do(conn, key, argv) // ❌ 错误:argv 被当作单个参数传入
应改为显式展开:
rScript.Do(conn,
"JJNb324a680c35d11e6a1123c15c2d271f21481871788G", // KEYS[1]
"30000", // ARGV[1] → TTL
"SSF_lastAccessedDate", "1481627386", // ARGV[2], ARGV[3]
"SSF_expiryDate", "2481657386", // ARGV[4], ARGV[5]
)
此时 Lua 中 #ARGV == 5,ARGV[1] 是 "30000"(TTL),后续为成对的 field-value。
✅ 优化 Lua 脚本逻辑
原脚本使用 table.getn()(已废弃)和手动索引构建数组,易出错且冗余。推荐改用 table.remove() 提前提取 TTL,并直接 unpack(ARGV):
if redis.call("EXISTS", KEYS[1]) == 1 then
local keyvalues = redis.call("HGETALL", KEYS[1])
local ttl = table.remove(ARGV, 1) -- 取出第一个参数作为 TTL
redis.call("HMSET", KEYS[1], unpack(ARGV)) -- 剩余参数自动展开为 field-value 对
redis.call("EXPIRE", KEYS[1], ttl)
return keyvalues
else
return 2 -- 表示 key 不存在
end
⚠️ 注意:
table.remove(ARGV, 1)会原地修改ARGV数组,确保unpack(ARGV)得到的是纯净的 field-value 列表。
✅ 正确解析返回值(关键!)
Redis 返回的 HGETALL 结果是 []interface{},每个元素为 []byte。直接 log.Printf("Result %+v", reply) 会打印字节切片(如 [116 121 112 101]),而非可读字符串。必须使用 Redigo 提供的类型转换辅助函数:
reply, err := rScript.Do(retryConn,
"JJNb324a680c35d11e6a1123c15c2d271f21481871788G",
"30000",
"SSF_lastAccessedDate", "1481627386",
"SSF_expiryDate", "2481657386",
)
if err != nil {
log.Fatal("Script error:", err)
}
// ✅ 正确解码为字符串切片(按 Redis HGETALL 格式:[field1, val1, field2, val2, ...])
strs, err := redis.Strings(reply, err)
if err != nil {
log.Fatal("Failed to convert reply to strings:", err)
}
log.Printf("HGETALL result: %v", strs) // 输出:["type" "2" "data" "Hello" ...]
✅ 最佳实践建议
-
脚本复用:将
redis.NewScript定义为包级变量,避免重复计算 SHA1 哈希:var hsetWithExpire = redis.NewScript(1, `...lua script content...`)
-
错误处理:始终检查
Script.Do返回的error,并用redis.Bool,redis.Int,redis.String等辅助函数安全解析结果。 -
连接管理:
cluster.Close()不应在main()开头defer(此时cluster尚未初始化完成),而应在defer cluster.Close()放在cluster.Refresh()成功之后。
综上,修正后的核心调用片段如下:
rScript := redis.NewScript(1, `if redis.call("EXISTS", KEYS[1]) == 1 then
local keyvalues = redis.call("HGETALL", KEYS[1])
local ttl = table.remove(ARGV, 1)
redis.call("HMSET", KEYS[1], unpack(ARGV))
redis.call("EXPIRE", KEYS[1], ttl)
return keyvalues
else
return 2
end`)
// 调用时显式展开所有参数
reply, err := rScript.Do(retryConn,
"JJNb324a680c35d11e6a1123c15c2d271f21481871788G",
"30000",
"SSF_lastAccessedDate", "1481627386",
"SSF_expiryDate", "2481657386",
)
if err != nil {
log.Fatal(err)
}
result, err := redis.Strings(reply, err)
if err != nil {
log.Fatal(err)
}
log.Println("Parsed result:", result)










