百万级群聊必须用 redis stream + 消费者组分发,配合 gorilla/websocket 连接池与心跳保活;连接管理用 sync.rwmutex + map[*websocket.conn]bool;广播时指针比较 conn != sender 剔除发送者;stream 写入需按 slot 分组 pipeline,且定时清理 pending 列表。

直接上结论:百万级大群聊群发不能靠单机内存广播,必须用 Redis Stream + 消费者组分发,配合 gorilla/websocket 的连接池化与心跳保活;否则 goroutine 泄露、写 panic、OOM、消息重复或丢失会集中爆发。
gorilla/websocket 连接管理必须用 RWMutex + map,别碰 sync.Map
sync.Map 不保证遍历一致性,广播时并发迭代可能 panic。真实线上服务中,map[*websocket.Conn]bool + sync.RWMutex 是唯一稳定选择。
- 注册连接:先
rwm.Lock(),插入后rwm.Unlock(),不要 defer - 广播前只读取:用
rwm.RLock()遍历,避免写阻塞读 - 连接关闭时,必须在 handler 内显式
delete(m, conn),且加锁——deferconn.Close()在 panic 场景下不执行 - 每个连接必须起独立 goroutine 调用
conn.ReadMessage(),否则一个卡住就堵死整条连接
群发必须剔除发送者自身,且只能用指针比较
用 conn == sender 判断即可,不要比 RemoteAddr、userID 或任何自定义 ID 字段——NAT、反向代理、长连接复用下这些值完全不可靠。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 错误写法:
if other.UserID != sender.UserID→ 多端登录、IP 池、CDN 后真实用户 ID 无法对齐 - 正确写法:
if conn != sender→ WebSocket 连接对象地址唯一,指针比较零成本、100% 稳定 - 若走 Redis Pub/Sub 中间层,发布前也必须从订阅 channel 中临时移除 sender 的 consumer group,不能依赖客户端 ignore
Redis Stream 是唯一可扩展的消息持久化方案
MySQL 存每条消息?QPS 上不去,主从延迟高,还拖垮群聊实时性。Stream 天然支持追加、分片消费、去重、断点续推,是百万群聊的事实标准。
- 写入统一走:
XADD chat:{roomID}:stream * msg(key 带{}确保同房间路由到同一 slot) - 新用户加入:用
XREVRANGE chat:{roomID}:stream + - COUNT 50拉最近 50 条 - 离线补推:建 consumer group(如
GROUP chat:{roomID} online_users),上线后XREADGROUP GROUP online_users me STREAMS chat:{roomID}:stream > - 别存
*websocket.Conn到 Redis —— 它不可序列化;只存结构体:{RoomID, UserID, Content, TS}
集群环境下 Pipeline 写 Stream 必须按 slot 分组
go-redis 的 ClusterClient 不支持跨 slot 的批量命令。直接 pipe.XAdd(...) 一堆不同 room 的 key,必然触发 CROSSSLOT 错误。
- 先对所有待写入的
chat:{roomID}:streamkey 提取 slot:redis.CRC16("chat:{1001}:stream") % 16384 - 按 slot 分组,每组起一个
ClusterClientpipeline(或复用已连通节点 client) - 每批严格控制在 100–300 条以内:太多易超 Redis 单包限制(proto-max-bulk-len),太少无合并收益
- 调用
pipe.Exec(ctx)后,必须遍历pipe.Cmds()检查每个cmd.Err()——Exec()只报连接级错误,单条XADD失败(如 key 被READONLY)会被静默吞掉
最常被忽略的一点:Stream 消费者组的 pending 列表不会自动清理。如果某 worker crash 后未 XCLAIM,积压消息会永远卡住;线上必须配定时任务扫描 XPENDING 并移交,否则“离线用户收不到历史消息”问题会悄无声息地持续数小时。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










