Subscribe后ReceiveMessage卡住主因是频道名错误却静默成功,需立即用超时ReceiveMessage验证是否返回redis.Nil,并用redis-cli手动SUBSCRIBE确认;Publish返回0仅表示无订阅者,非发送失败,真正异常看ret.Err()。

Subscribe 后 ReceiveMessage 卡住或永远收不到消息
这是最常被忽略的“静默失败”——Subscribe 不校验频道是否存在,拼错名、大小写不一致、多空格,它都成功返回,但后续 ReceiveMessage() 就再也不会吐出任何消息。
真正该检查的是:订阅后立刻调用一次 ReceiveMessage() 并设超时,看是否在几秒内返回 redis.Nil(说明无活跃订阅者)还是真实错误;同时用 redis-cli 手动 SUBSCRIBE topic_name 验证频道名是否拼对。
- 别依赖
err != nil就 break —— 要用errors.Is(err, redis.Nil)忽略心跳超时,errors.Is(err, context.Canceled)区分主动退出 - 网络抖动或 Redis 重启会导致
ReceiveMessage()返回非空 err,此时应重连并重新Subscribe,而不是直接 panic 或 exit - 一个
*redis.PubSub实例可同时监听多个频道,但所有消息走同一个ch := sub.Channel()流,必须靠msg.Channel字段区分来源
发布端返回 0 是不是发失败了?
不是。rdb.Publish(ctx, "topic", data) 的返回值 ret.Val() 是「当前在线且已成功订阅该频道的客户端数量」,不是发送成败标志。返回 0 只代表此刻没人在线听,不等于消息没发出去。
真正反映业务异常的是 ret.Err():比如 Redis 密码错误、ACL 拒绝 PUBLISH 权限、网络不通——这些才会触发非 nil 错误。
立即学习“go语言免费学习笔记(深入)”;
- 如果业务要求“至少一人收到”,得自己实现确认机制(例如让接收方回传 ACK 到另一个 channel)
- 别把
Publish当同步 RPC 用——它发完就返回,不等消费,也不管下游处理快慢 - 高频发布时注意 Redis 的
client-output-buffer-limit pubsub配置,默认 32MB,溢出会强制断开慢订阅者
为什么断连后消息全丢了?怎么补救
因为原生 Pub/Sub 是纯内存广播,不持久化、不存历史、不支持重放。客户端掉线 1 秒,期间所有消息永久消失。
这不是 Go 客户端的问题,是 Redis 协议设计如此。想保消息,必须换方案:
- 用
Redis Stream替代:支持消费者组、消息 ID 追溯、ACK 确认、自动重试,XADD/XREADGROUP是正解 - 加一层本地缓冲(如带 TTL 的 map + goroutine 定时清理),只缓最近几秒消息,治标不治本
- 关键业务绝不依赖纯 Pub/Sub 做核心通知,比如订单状态变更、支付结果,必须走 DB + 消息队列双写
goroutine 泄漏和连接堆积怎么防
PubSub 实例是独立 TCP 连接,defer pubsub.Close() 忘写,或者 Subscribe 后没启动接收循环,就会导致 Redis 的 CLIENT LIST 中 idle 连接越积越多,最终耗尽连接数。
更隐蔽的是:在 HTTP handler 里每请求起一个 Subscribe,但没控制生命周期,请求结束连接却没关。
- 每个
Subscribe必须配对defer pubsub.Close(),且确保它真能执行到(比如不要放在 if 分支里漏掉) - 接收逻辑务必包在
for循环里,并监听ctx.Done()主动退出,不能靠 panic 或 return 漏掉 close - 用
redis-cli CLIENT LIST | grep pubsub定期查连接数,上线前压测时重点盯这个指标
Pub/Sub 表面简单,实际是陷阱密集区:静默失败、零容忍断连、连接泄漏、语义模糊的返回值……它适合做内部服务间轻量通知,但一旦涉及可靠性、追溯、重试,就得果断切到 Stream 或专业消息中间件。










