
本文详解 redigo 调用 redis lua 脚本时常见的“wrong number of args”错误成因,提供参数传递规范、lua 脚本优化写法,并演示如何将二进制响应正确解码为可读字符串。
本文详解 redigo 调用 redis lua 脚本时常见的“wrong number of args”错误成因,提供参数传递规范、lua 脚本优化写法,并演示如何将二进制响应正确解码为可读字符串。
在使用 Redigo 执行 Lua 脚本时,ERR Error running script ... Wrong number of args calling Redis command From Lua script 是典型且高频的报错。其根本原因在于:Redis Lua 环境中 redis.call() 对参数数量有严格校验,而 Go 层传入的 []string 参数未被正确展开为独立参数,导致 unpack(a) 实际只返回单个值(如 nil 或空表),进而使 HMSET 接收了非法参数个数(例如仅 2 个而非偶数个 field-value 对)。
✅ 正确的参数传递方式
Redigo 的 Script.Do() 方法不接受 []string 切片作为单个参数,而是要求将每个 KEYS 和 ARGV 元素以独立参数形式传入。错误写法:
argv := []string{"30000", "SSF_lastAccessedDate", "1481627386", "SSF_expiryDate", "2481657386"}
rScript.Do(conn, "mykey", argv) // ❌ 错误:argv 被当做一个整体参数
✅ 正确写法(显式展开):
reply, err := rScript.Do(conn,
"JJNb324a680c35d11e6a1123c15c2d271f21481871788G", // KEYS[1]
"30000", // ARGV[1] → TTL
"SSF_lastAccessedDate", "1481627386", // ARGV[2], ARGV[3]
"SSF_expiryDate", "2481657386", // ARGV[4], ARGV[5]
)
? 提示:
NewScript(1, script)中的1表示脚本声明 1 个 key(即KEYS[1]),其余均为ARGV参数,务必与 Lua 中KEYS/ARGV引用逻辑一致。
✅ 优化 Lua 脚本逻辑
原始脚本中手动构建数组 a 并用 table.getn() 遍历的方式易出错且低效。推荐使用 table.remove() 提前提取 TTL,并直接 unpack(ARGV) —— 更简洁、更健壮:
if redis.call("EXISTS", KEYS[1]) == 1 then
local keyvalues = redis.call("HGETALL", KEYS[1])
local expires = table.remove(ARGV, 1) -- 提取第一个参数作为过期时间
local res = redis.call("HMSET", KEYS[1], unpack(ARGV)) -- 剩余参数直接用于 HMSET
redis.call("EXPIRE", KEYS[1], expires)
return keyvalues
else
return 2 -- Key doesn't exist
end
✅ 正确解析二进制返回值
Redis 返回的 HGETALL 结果是 [][]byte(字节切片的切片),直接打印会显示 ASCII 码(如 [116 121 112 101] 对应 "type")。必须使用 Redigo 提供的类型转换辅助函数:
// 将 []interface{}(含 []byte)安全转为 []string
stringSlice, err := redis.Strings(reply, err)
if err != nil {
log.Fatal("Failed to parse reply:", err)
}
log.Printf("Parsed result: %+v", stringSlice)
// 输出:["type" "2" "data" "Hello" ...]
⚠️ 注意:
redis.Strings()会验证结果长度是否为偶数(符合 hash 结构),若脚本返回非标准格式(如数字2),需先做类型判断:if val, ok := reply.(int64); ok && val == 2 { log.Println("Key does not exist") } else if strings, err := redis.Strings(reply, err); err == nil { // 处理 HGETALL 结果 }
? 完整最佳实践示例
var rScript = redis.NewScript(1, `
if redis.call("EXISTS", KEYS[1]) == 1 then
local keyvalues = redis.call("HGETALL", KEYS[1])
local expires = table.remove(ARGV, 1)
redis.call("HMSET", KEYS[1], unpack(ARGV))
redis.call("EXPIRE", KEYS[1], expires)
return keyvalues
else
return 2
end`)
// 在 main() 中调用:
reply, err := rScript.Do(retryConn,
"JJNb324a680c35d11e6a1123c15c2d271f21481871788G",
"30000",
"SSF_lastAccessedDate", "1481627386",
"SSF_expiryDate", "2481657386",
)
if err != nil {
log.Fatal("Script execution failed:", err)
}
// 安全解析
if intVal, ok := reply.(int64); ok {
if intVal == 2 {
log.Println("Key not found")
}
} else if strSlice, err := redis.Strings(reply, nil); err == nil {
log.Printf("Hash fields: %v", strSlice) // 格式化输出:["field1" "val1" "field2" "val2"]
} else {
log.Fatal("Cannot parse reply as strings:", err)
}
通过遵循参数显式传递、Lua 脚本精简、返回值强类型解析三原则,即可彻底规避 Wrong number of args 错误,并获得与 redis-cli 一致的可读结果。










