go-redis/v8 pub/sub核心三点:必须用独立*redis.pubsub实例、receivemessage需循环调用、不close会连接泄漏;混用client会panic,需超时验证订阅有效性,多频道须手动分流,务必显式close并实现重连退避。

用 go-redis/redis/v8 做 Redis Pub/Sub,核心就三点:必须用独立 *redis.PubSub 实例、ReceiveMessage 要循环调、不 Close() 就泄漏。其他问题基本都从这三处衍生。
Subscribe 必须用新 client,不能复用普通 redis.Client
redis.Client 是为 GET/SET 等命令设计的;Subscribe 返回的 *redis.PubSub 底层独占一条 TCP 连接,且禁止执行任何非 Pub/Sub 命令。混用会 panic 或静默失败。
- 错误写法:
client.Subscribe(ctx, "order.created")后立刻调client.Get(ctx, "x")→ 报redis: nil或连接重置 - 正确做法:新建专用 client(或至少确保生命周期隔离),例如
subClient := redis.NewClient(opts),再subClient.Subscribe(...) - 连接池开销可控,但逻辑耦合的代价是查两小时收不到消息
ReceiveMessage 不返回 ≠ 订阅失败,得加超时主动验证
Subscribe 成功不等于频道名对;Redis 对错名、大小写、空格、连错 DB 都不报错,只安静等待。结果就是 ReceiveMessage 一直挂起,CPU 低、日志静默、服务像卡住。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 上线前必做:订阅后立即用带超时的
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)调一次pubsub.ReceiveMessage(ctx) - 若返回
context.DeadlineExceeded或redis.Nil,立刻用redis-cli手动验证:PUBSUB NUMSUB order.created看是否为 0,PUBSUB CHANNELS看频道是否在列表里 - 别只检查
err != nil就 break —— 要用errors.Is(err, redis.Nil)忽略心跳超时,errors.Is(err, context.Canceled)区分主动退出
多个频道共用一个 ReceiveMessage,靠 msg.Channel 分流
你可以一次 Subscribe(ctx, "user.updated", "order.created"),但所有消息都走同一个 ReceiveMessage 流,不会自动按频道拆 goroutine 或 channel。
- 必须手动判断:
if msg.Channel == "user.updated" { ... },别指望自动路由 - 如果业务逻辑差异大(比如一个要查 DB,一个只发通知),建议拆成多个独立
Subscribe + goroutine,避免慢操作阻塞快通道 - Redis 不保证跨频道消息顺序,只保证单频道内 FIFO;别在多个频道间做强时序依赖
不 Close 就泄漏,重连逻辑得自己写
pubsub.Close() 不是可选项,是释放连接的唯一方式。不调它,Redis 的 CLIENT LIST 里 idle 连接越积越多,最终触发 maxclients 拒绝新连接。
- 务必
defer pubsub.Close(),尤其在 goroutine 中启动的订阅 -
ReceiveMessage可能因网络抖动、Redis 重启返回真实 error,此时需手动重连 + 重新Subscribe,并加入指数退避(如 1s、2s、4s…) - 高频发布时注意 Redis 的
client-output-buffer-limit pubsub配置,默认 32MB,溢出会强制断开慢订阅者
最常被忽略的是:订阅后没循环调 ReceiveMessage,消息就堆在 TCP 缓冲区里,既不报错也不触发处理;还有就是把 Publish 当同步 RPC 用,其实它发完就返回,不等消费、不管下游快慢。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










