
面对数万级并发 websocket 连接,避免使用带单 mutex 的 map 维护连接列表,转而采用基于发布/订阅的去中心化通信模型,并结合分布式消息中间件实现水平扩展。
在高并发实时通信场景中(如每 100ms 向 >50,000 个活跃客户端广播消息),传统“维护全局连接列表 + 单锁遍历发送”的方式存在严重瓶颈:
- 单 sync.Mutex 成为串行热点,写(连接/断开)与读(广播)相互阻塞;
- 内存中存储全部 *websocket.Conn 实例导致 GC 压力陡增、内存占用线性膨胀;
- 无法横向扩展——单机容量见顶后难以平滑扩容。
✅ 正确架构思路:解耦连接管理与消息分发
每个 WebSocket 连接不再被“集中注册”,而是作为独立消费者,通过唯一标识(如 user_id、session_id 或 room:general)向中心化消息枢纽(Hub)订阅(Subscribe) 相关主题。消息生产者(如业务 goroutine)仅需向对应主题 发布(Publish),由底层消息系统完成路由与投递。
// 示例:客户端连接时订阅自身主题(Go + Gorilla WebSocket + NSQ 客户端)
func handleWS(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
// 生成或提取连接标识(建议从 JWT / Cookie / URL 参数获取)
userID := extractUserID(r)
// 启动订阅协程:监听专属主题(如 "user:12345")和全局主题(如 "broadcast")
go subscribeToNSQ(userID, conn)
// 心跳与读取逻辑...
}? 关键技术选型建议:
- 消息中间件:选用专为高吞吐设计的 Go 原生方案,如 NSQ(轻量、最终一致性、支持 topic/channel 分层)、NATS(低延迟、支持 JetStream 持久化)或 Apache Pulsar(多租户、分层存储)。避免自研“带锁 map”或 Redis Pub/Sub(后者无连接状态感知、易丢消息)。
- 连接生命周期管理:由 WebSocket handler 自行处理 OnClose,向消息系统发送 unsubscribe 事件(或依赖心跳超时自动清理);不依赖中心 map 的增删操作。
- 广播优化:对全量推送,可发布至 broadcast topic,由 NSQ 的 channel 复制机制天然支持多消费者并行消费;若需精准定向(如仅推给某城市用户),则用 region:shanghai 等语义化 topic。
⚠️ 注意事项:
- 绝不在线程不安全的结构中直接调用 conn.WriteMessage() —— WebSocket 连接非并发安全,每个连接应绑定独立写协程(或使用带锁的 writer wrapper);
- 连接标识必须全局唯一且稳定,避免因重复 ID 导致消息错投;
- 监控与降级:对 NSQ topic 的 depth、backend_depth 及 consumer finish_count 实时告警,当积压过高时可启用消息采样或降级为轮询拉取。
总结:规模化的实时服务不是“管住所有连接”,而是“让连接自主发现所需消息”。放弃中心化连接列表,拥抱发布/订阅范式 + 分布式消息总线,是支撑 50K+ WebSocket 连接稳定、低延迟、可伸缩的工程共识。










