不调exec()命令静默丢失;不检查每个cmd.err()就放任失败;pipeline是手动触发的批量队列,非自动执行器。

不调 Exec() 就等于没发,命令静默丢失;不检查每个 Cmd.Err() 就等于放任失败——Pipeline 不是自动执行器,而是手动触发的批量队列。
为什么 client.Pipeline() 后不调 Exec() 就没效果
Pipeline 对象只是内存里的命令缓冲区,Set()、Get() 等调用只是往里塞指令,并不发包。整个过程没有网络 I/O,也不校验语法或 key 类型。
-
pipe := client.Pipeline()之后所有命令都只是排队,不会触发任何 Redis 请求 - 如果忘记
pipe.Exec(ctx),程序照常运行、无 panic、无 error、无日志,但 Redis 根本收不到任何数据 - 尤其在 if 分支或 defer 里漏掉
Exec(),会导致缓存预热失败、排行榜写空、启动卡顿却查不出原因
怎么正确取结果:别只看 Exec() 的 error
Exec() 返回的 error 只反映“整批发送是否成功”(比如连接断开、超时),不代表每条命令都 OK。真正承载单条命令成败的是返回的 *redis.Cmd 实例。
-
setCmd := pipe.Set(ctx, "k", "v", 0)返回的是一个未执行的句柄,setCmd.Val()和setCmd.Err()都是空的,直到Exec()完成后才可读 - 必须对每个 cmd 单独调
.Err():比如if err := setCmd.Err(); err != nil { ... } - 常见错误如
ERR wrong number of arguments、WRONGTYPE Operation against a key holding the wrong kind of value都藏在具体 cmd 里,Exec()的 error 可能仍是nil
Redis Cluster 下不能直接用 client.Pipeline()
Cluster 节点只处理属于自己 slot 的 key。把不同 slot 的 key 塞进同一个 Pipeline,节点会直接拒收,报错类似 CROSSSLOT Keys in request don't hash to the same slot。
- 先算 slot:
slot := clusterClient.KeySlot("{user:123}:profile") - 再找节点:
node := clusterClient.NodeForKey(slot)或遍历clusterClient.Nodes()匹配 - 最后在该
*redis.Client上调node.Pipeline()—— 此时它退化为单机行为,不再跨 slot 校验 - 更稳妥的做法:优先用
MGET/MSET/ZADD(带多个 member)等原生命令,它们内部已做 slot 分发,比手撸 Pipeline 更稳
什么时候该用 Pipeline,什么时候该砍掉
Pipeline 不是银弹。它只在「命令彼此独立、数量 ≥10、且 RTT 成瓶颈」时带来真实收益。其他情况反而增加维护成本和出错面。
- 适合场景:
bulkAddUsers(万级用户预热)、bulkUpdateLeaderboard(百个 UID 批量 ZAdd)、日志聚合写入(几十个LPUSH) - 不适合场景:读 3 条配置、写 2 个字段、或下一个命令依赖上一个结果(如先
INCR再判断值)——这种得拆成多次调用或改用 Lua - 大 key 要小心:
value > 10KB时打包多条,可能突破proto-max-bulk-len限制,或撑爆客户端内存 - 超时设置要谨慎:
context.WithTimeout(ctx, 50ms)会让整批命令在第 1 条卡住时全部中断,而不是重试或跳过
最容易被忽略的其实是错误粒度和超时边界:Pipeline 把网络层、协议层、命令层的失败混在一起,不逐个 inspect Cmd,就永远不知道哪条命令悄悄失败了。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











