必须用独立 client 订阅,因 *redis.pubsub 独占 tcp 连接且不支持普通命令;receivemessage 阻塞等待消息,频道名错误或连错实例均无报错,需加超时并手动校验;多频道共用 receivemessage 需手动分流;务必 close 释放连接,重连需指数退避。

Subscribe 必须用独立 client,不能复用普通操作 client
一个 *redis.Client 是为命令操作(GET、SET、HGETALL)设计的,而 Subscribe 返回的是 *redis.PubSub,它底层会独占一条 TCP 连接,且不支持任何非 Pub/Sub 命令。混用会导致 panic 或静默失败。
- 错误写法:
client.Subscribe(ctx, "order.created")后紧接着调client.Get(ctx, "x")—— 会报redis: nil或连接被重置 - 正确做法:新建专用 client 实例,或至少确保
PubSub实例生命周期与主 client 完全隔离 - 别省那几行代码去复用 client;连接池开销可控,但逻辑耦合的代价是调试两小时找不到为什么收不到消息
ReceiveMessage 是阻塞调用,频道名错也不会报错
pubsub.ReceiveMessage(ctx) 会一直挂起,直到有消息到达——前提是订阅成功。而 Redis 的 SUBSCRIBE 不校验频道是否存在,拼错名(比如多空格、大小写不一致、前缀漏了 ws:)、连错 DB、甚至连错 Redis 实例,都不会返回 error,只会永远等下去。
- 典型现象:日志里没报错,但
ReceiveMessage从不返回,CPU 占用低,服务像“卡住”一样安静 - 排查建议:用
redis-cli手动执行PUBSUB NUMSUB order.created看订阅数是否为 0;再用PUBSUB CHANNELS确认频道名是否真在活跃列表里 - 上线前务必加兜底超时:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second),收到context.DeadlineExceeded就该立刻 log 并重启订阅 goroutine
多个频道共用一个 ReceiveMessage,靠 msg.Channel 分流
你可以一次 Subscribe 多个频道:client.Subscribe(ctx, "user.updated", "order.created", "payment.confirmed"),但所有消息都走同一个 ReceiveMessage 流,不会按频道分 channel 或 goroutine。
- 必须手动判断:
if msg.Channel == "user.updated" { ... },别指望自动路由 - 如果业务逻辑差异大(比如一个要查 DB,一个只发通知),建议拆成多个独立
Subscribe+ goroutine,避免慢操作阻塞快通道 - 注意:Redis 不保证跨频道消息顺序,只保证单频道内 FIFO;别在多个频道间做强时序依赖
不 Close 就泄漏,且重连逻辑得自己写
pubsub.Close() 不只是礼貌,是必须。不调它,连接不会释放,Redis 的 CLIENT LIST 里 idle 连接越积越多,最终触发 maxclients 拒绝新连接。
-
defer pubsub.Close()在循环里无效——因为 defer 绑定的是当前迭代的变量,而循环中 pubsub 被反复覆盖;必须显式调pubsub.Close()再 break - 网络抖动、Redis 重启后,
ReceiveMessage会返回 error,此时要主动 close 并重建整个Subscribe流程,不能只重试ReceiveMessage - 生产环境别裸写 for-loop 重连,建议封装带指数退避的 helper,比如失败后 sleep 1s → 2s → 4s,避免雪崩式重连冲击 Redis
Close 的时机、context 的传递、以及错误分支是否真正覆盖了所有断连路径里。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











