pipeline没生效是因为未显式调用exec(),命令仅暂存内存而未发送;exec()后需逐个检查各*redis.cmd的err()或result(),集群下还需按slot分组路由。

不调 Exec() 就等于没发命令,Pipeline 不是自动触发的魔法,它只是把命令攒在内存里等你拍板。
为什么 Pipeline 没生效?——Exec() 必须显式调用
很多人写了十几条 Set()、ZAdd(),最后忘了 pipe.Exec(ctx),程序既不报错也不写入 Redis。命令就静静留在内存里,随 GC 回收,数据彻底丢失。
-
Exec()是唯一真正发包的入口;不调它,网络层零流量 - 调了
Exec()后,所有命令才按顺序一次性发往 Redis,返回一个聚合错误(如连接超时),但不反映单条命令成败 - 每条命令对应的
*redis.Cmd(比如*redis.StringCmd)必须单独调.Result()或.Val()才能取值,且要检查cmd.Err() - 常见误写:
pipe.Set(...); pipe.Get(...); pipe.Exec(...); fmt.Println(pipe.Get(...).Val())—— 最后那个Get()根本没进 Pipeline,也不会被执行
什么时候该用 Pipeline?——批量、独立、高 RTT 场景才值得
不是所有“多条命令”都适合 Pipeline。它解决的是网络往返开销,不是逻辑封装。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 适用场景:缓存预热(启动时塞几万条
user:1001)、排行榜批量写入(几十个ZAdd())、批量删除(Del()多个 key) - 不适用场景:只读 3 条、写 2 个字段;或命令间有依赖(比如用
Get()结果决定下一条Set()的 value) - 优先考虑原生命令:能用
MGET/MSET就别手撸 Pipeline,它们内部已做 slot 分发、更稳、更少出错 - 量级门槛:单次 ≥10 条独立命令,RTT 成为主要瓶颈时,收益才明显
集群环境下 Pipeline 怎么写?——不能直接 rdb.Pipeline()
Redis Cluster 不允许跨 slot 打包命令。{user:123}:profile 和 {order:456}:meta 很可能落在不同节点,直接 Pipeline 会被拒绝。
- 必须先算 slot:
slot := clusterClient.KeySlot("user:123") - 再查节点:
node := clusterClient.NodeForKey(slot)或遍历clusterClient.Nodes() - 最后在该
*redis.Client上调node.Pipeline()—— 此时它退化为单机行为 - 如果 key 数量大、分布散,手动分组 + 多个 Pipeline 更安全,但代码复杂度陡增
- 更省事的替代:用
MGET/MSET,它们由 go-redis 自动按 slot 路由并并发执行
超时、错误、大 key ——三个最容易被忽略的坑
Pipeline 的失败模式和单条命令完全不同:一次发、一批回、错误分散在每个 Cmd 里,不是集中在 Exec()。
- 超时设置要分层:连接池的
ReadTimeout影响整个 Pipeline 响应,建议设为 3s 左右,避免卡死 - 错误必须逐个检查:
if err := setCmd.Err(); err != nil { ... },不能只看Exec()返回的 error - 大 key 风险:Pipeline 不会压缩 payload,100 个 1MB 的
Set()会一次性发 100MB 到 Redis,可能触发 client-output-buffer-limit 或 OOM - 调试技巧:开启 go-redis 的
DebugWriter,打印实际发出的 Redis 协议内容,确认是否真发出去了
真正难的从来不是“怎么写 Pipeline”,而是判断哪条路径该走 Pipeline、哪条该用原生命令、哪条干脆不该批量——边界模糊,得结合 key 分布、错误容忍度、监控指标一起看。










