pipe.Exec()必须显式调用,否则命令仅暂存内存而未发送;Exec()后需逐个检查各*redis.Cmd的Err()或Result(),集群下还需按slot分组路由到对应节点执行。

pipe.Exec() 必须显式调用,否则所有命令只是暂存在内存里,Redis 服务器完全无感知,数据静默丢失。
为什么 pipe.Exec() 不调就等于没发命令
Go-Redis 的 Pipeline() 是惰性构造的——它只初始化一个命令缓冲区(底层是 []*redis.Cmd),每次调用 Set()、Get() 等方法,只是往 slice 里追加一个未执行的指令对象。网络 I/O 完全不触发。
常见错误现象:
- 调试时发现 Redis 日志空空如也,
KEYS *查不到写入的 key - 程序运行无报错,但
Get()返回空或 nil,误以为是逻辑问题 - 重复调用
pipe.Exec(ctx)导致 panic:exec: pipeline already closed
实操建议:
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 每个
pipe := client.Pipeline()后必须且仅能配对一次pipe.Exec(ctx) - 把
Exec()放在命令链末尾,不要插在中间或漏掉 - 避免在
Exec()前做耗时操作(如 DB 查询、HTTP 调用),否则“省 RTT”优势被抵消
如何正确取每条命令的结果并检查错误
pipe.Exec(ctx) 返回的 error 只反映网络层失败(如连接断开、超时),不代表某条命令是否成功执行。真正结果和错误分散在每个 *redis.Cmd 实例中。
立即学习“go语言免费学习笔记(深入)”;
常见误写:
-
pipe.Get(ctx, "k"); pipe.Exec(ctx); fmt.Println(pipe.Get(ctx, "k").Val())—— 最后那个Get()根本没进 pipeline,返回的是新命令的空指针 - 直接用
setCmd.Val()取写操作结果,类型断言 panic(*redis.StatusCmd没有Val()方法)
实操建议:
- 读操作(
Get、HGetAll)优先用.Result(),它内部已合并.Val()和.Err() - 写操作(
Set、ZAdd)用.Err()判断是否成功 - 数值类(
Incr、Decr)必须用.Int64()或.Uint64(),别用.Val() - 即使
Exec()成功,也要遍历每个 cmd 检查cmd.Err() != nil
集群环境下 Pipeline() 为什么 panic 或被拒绝
*redis.ClusterClient 不允许直接调用 Pipeline(),go-redis 会 panic;即使绕过限制强行打包跨 slot 的命令,Redis Cluster 也会在服务端拒绝执行(CROSSSLOT Keys in request don't hash to the same slot)。
原因在于:Pipeline 是客户端批量发送机制,不带 slot 路由逻辑;而 Cluster 要求同一批命令的所有 key 必须落在同一节点上。
实操建议:
- 绝对不要写
clusterClient.Pipeline(),编译期不会报错,运行时 panic - 若 key 数量少且前缀集中(如
user:1001、user:1002),先算 slot:slot := clusterClient.KeySlot("user:1001"),再查节点:node := clusterClient.NodeForKey(slot),最后在node上调Pipeline() - 若 key 分布散、数量大(如预热全量缓存),优先改用原生命令:
MGET(keys...)、MSET(kvs...),它们由 go-redis 自动按 slot 分组并发执行,更稳、更少出错
什么场景该用 Pipeline,什么场景该换 MGET/MSET 或 Lua
Pipeline 解决的是「网络往返延迟」,不是「逻辑封装」或「原子性」。它适合高 RTT、大批量、彼此独立的命令;不适合小批量、强依赖或需事务语义的场景。
适用场景(推荐 Pipeline):
- 微服务启动时缓存预热:塞几万条
user:1001、config:prod - 排行榜批量更新:几十个
ZAdd("leaderboard", redis.Z{Member: "uid1", Score: 95}) - 批量清理:
Del("tmp:1", "tmp:2", ...)传入上百个 key
不适用场景(换方案):
- 只读 3–5 条数据 → 直接用
MGET(keys...),代码更短、兼容集群、性能不输 Pipeline - 命令间有依赖(如
Get()结果决定是否Set())→ 改用 Lua 脚本或串行执行 - 需要扣库存 + 写订单这种原子性 → Pipeline 不提供事务,必须用 Lua 或应用层锁
- 单次命令数 10KB → 打包后易超
proto-max-bulk-len,反而触发 Redis 拒绝或客户端 OOM
最容易被忽略的点:Pipeline 的错误不是“全成功”或“全失败”。可能第 3 条因类型错误返回 WRONGTYPE,但第 4–10 条仍正常执行并返回结果——必须逐个检查每个 cmd.Err(),不能只看 Exec() 的 error。

















