subscribe()成功不等于订阅生效,需监听redis.subscription消息确认;receivemessage()可能返回三类错误,须分类处理;publish返回值为订阅数非发送结果,0仅表示无活跃订阅者。

Subscribe() 调用后没报错,但收不到消息?先看 Subscription 消息
这是最常被忽略的“静默失败”:你调用 rdb.Subscribe(ctx, "order.created") 成功返回了 *redis.PubSub,但它**根本没告诉你是否真连上了频道**。Redis 的 SUBSCRIBE 命令响应是异步推送的,真正确认订阅成功的信号,是后续从 ps.Channel() 流里收到的 redis.Subscription 类型消息。
- 必须显式监听并判断:
msg := ps.ReceiveMessage(ctx)后,检查msg.Channel == "" && msg.Pattern == ""且msg.Kind == "subscribe",再看msg.Count >= 1才算生效 - 别在第一次
ReceiveMessage()返回就开处理业务逻辑——前 1–2 条大概率是subscribe或psubscribe状态消息,跳过它们会导致漏掉第一条业务消息 - 频道名大小写、空格、前缀不一致(比如
"user.update"vs"user.updated")不会触发错误,只会让订阅永远“悬空”,务必和发布端严格对齐
ReceiveMessage() 卡死或 panic?不是 bug,是没处理三类典型错误
ReceiveMessage() 是阻塞读,底层依赖 Redis 的响应流,网络抖动、服务重启、context 取消都会让它返回非 nil 错误——但这些错误语义完全不同,统一 if err != nil { break } 就等于主动放弃整个 goroutine。
-
errors.Is(err, context.Canceled):正常退出,该defer ps.Close()了 -
errors.Is(err, redis.Nil):心跳超时或空响应,可忽略,继续循环 - 其他任意
err(如read: connection reset by peer、redis: connection closed):连接已断,应记录日志 + 重建PubSub实例,而不是 break - 别在循环里加
time.Sleep(100 * time.Millisecond)等待——这会放大延迟,所有超时控制必须通过ctx统一管理,例如ctx, cancel := context.WithTimeout(parentCtx, 30*time.Second)
发布端 ret.Val() == 0 是失败吗?不是,只是“此刻没人在线”
rdb.Publish(ctx, "topic", data) 返回的 ret.Val() 是**当前已成功订阅该频道的客户端数量**,不是“发送是否成功”的布尔值。它为 0 不代表消息丢了,只说明此时没有活跃订阅者。
- 判断业务是否真正发出,只看
ret.Err():返回非 nil 才表示网络异常、ACL 拒绝、密码错误等真实问题 - Pub/Sub 天然不存历史消息,断连期间发布的全部丢失——如果需要可靠投递,别硬扛,直接换
Redis Stream(支持 consumer group、ack、重播) - 多个订阅者监听同一频道,消息会广播给全部,但彼此完全隔离;一个订阅者崩溃不影响其他,但也不会自动重连恢复订阅状态
为什么消息突然开始丢?PubSub 缓冲区太小 + 回调没兜底
redis.PubSub 内部缓冲区默认只有 10 条,且**完全不报丢消息的错**。一旦你的消息处理回调(比如解析 JSON、调用 HTTP 接口)变慢或 panic,新消息就会无声无息地覆盖旧消息。
- 每个消息处理必须包一层
defer func() { recover() }(),否则 panic 会直接终止整个ReceiveMessage()循环 - 给回调加硬性超时:
ctx, _ := context.WithTimeout(parentCtx, 5*time.Second),避免单条消息拖垮全局 - 别复用同一个
PubSub实例订阅多个业务无关频道(比如同时订"payment.success"和"log.error")——一个频道消息积压或异常,会卡住所有频道的消息消费 -
PubSub不是线程安全的,也不能当全局单例;每次重连都得重建实例,并在NewPubSub前用client.Ping(ctx).Err()检查连接健康度
真正麻烦的从来不是怎么写通,而是连接断了要不要重试、消息丢了要不要补偿、回调 panic 了能不能捞回来——而 Redis Pub/Sub 这三样全不提供。它轻、快、简单,也正因如此,所有容错逻辑都得你亲手补上。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











