Redis Pub/Sub 是一种基于频道的异步消息通信模式,发布者与订阅者通过频道解耦,支持多对多实时广播,但消息不持久化、无ACK机制、不保证可达性。

redis.PubSub 不是“连上就能收”,它不报错、不重试、不存历史——你得亲手拉住它的每一步,否则消息就静默消失。
Subscribe() 返回成功 ≠ 已订阅生效
调用 rdb.Subscribe(ctx, "chat:room1") 后拿到 *redis.PubSub 实例,不代表已经监听到消息。Redis 的 SUBSCRIBE 响应是异步推送的,真正确认成功的信号,是后续从 ps.Channel() 流里收到一条 redis.Subscription 类型消息。
- 必须显式判断:
msg.Kind == "subscribe" && msg.Count >= 1,且msg.Channel == "chat:room1" - 前 1–2 条消息大概率是
subscribe或psubscribe状态消息,直接处理业务逻辑会漏掉第一条真实消息 - 频道名大小写、空格、前缀不一致(比如
"chat:room1 "多个空格)不会报错,但永远收不到——发布端和订阅端必须严格字符串相等 - ACL 权限不足(如没开
subscribe或psubscribe)也会静默失败,建议上线前用redis-cli -a手动验证auth && redis-cli subscribe test
ReceiveMessage() 卡住或 panic 的真实原因
ReceiveMessage() 是阻塞读,底层依赖 Redis 的响应流。它可能返回三类值,统一 if err != nil { break } 就等于主动放弃整个 goroutine。
-
errors.Is(err, context.Canceled):正常退出,该defer ps.Close()了 -
errors.Is(err, redis.Nil):Redis 心跳超时或空响应,可忽略,继续循环 - 其他任意
err(如"read: connection reset by peer"、"redis: connection closed"):连接已断,需记录日志 + 重建*redis.PubSub实例 - 别在循环里加
time.Sleep(100 * time.Millisecond)—— 这会放大延迟,所有超时控制必须通过ctx统一管理,例如ctx, cancel := context.WithTimeout(parentCtx, 30*time.Second)
Publish() 返回 0 不代表失败,只是“此刻没人在线”
rdb.Publish(ctx, "chat:room1", data) 的 ret.Val() 是**当前已成功订阅该频道的客户端数量**,不是“发送是否成功”的布尔值。
- 返回 0:只说明此刻没有活跃订阅者,不等于网络失败或权限错误;
ret.Err()才反映真实问题(如密码错误、ACL 拒绝PUBLISH权限) - 返回非 0 数字:也不代表每条消息都送达,Pub/Sub 无 ACK,无法保证可达性
- 若需“至少一次”语义,必须自己实现 ACK 机制:订阅者处理完后向另一个频道发确认,发布者监听并做幂等
- 生产环境建议搭配心跳检测:
ps.Ping(ctx)定期探测连接状态,失败则重建实例(注意:别用ps.Ping()测试连通性——它会发到所有已订阅 channel,可能触发误通知)
每个客户端连接必须配独立 PubSub 实例
多个 goroutine 并发调用同一个 *redis.PubSub 的 ReceiveMessage() 会 panic;共用一个实例还容易因某房间异常导致其他房间消息中断。
- 每个 WebSocket 连接 / HTTP 长连接应创建独立
*redis.PubSub实例,对应独立 goroutine 调用ReceiveMessage() - 务必
defer ps.Close(),否则连接泄漏,Redis 侧会堆积client list中的 idle 连接 - 订阅多房间时,用
ps.Subscribe(ctx, "chat:room1", "chat:room2")一次性订阅,靠msg.Channel区分来源,不要为每个房间建新实例(除非隔离性要求极高) - 频道名必须校验:长度 ≤64 字符,仅允许字母、数字、下划线;禁止空格、星号、控制字符,防止注入或路由混乱
最易被忽略的是缓冲区容量和消息结构封装:默认 redis.PubSub 内部缓冲区只有 10 条,一旦消息处理变慢或 panic,新消息会无声覆盖旧消息;而直接拼接字符串 publish 或裸传 struct{},会导致接收端解析失败或中文乱码——json.Marshal 和 json.Unmarshal 是唯一安全路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











