goroutine泄漏的根本原因是每个goroutine缺乏明确退出路径;常见场景包括channel阻塞、context未取消、无限循环无退出及waitgroup误用,需通过runtime.numgoroutine监控、pprof分析调用栈定位,并结合context控制、超时设置、channel关闭和done调用预防。

直接用 melody.New() 启动就能跑,但真要扛住高并发、不丢消息、不泄漏 goroutine,得绕开几个默认配置的坑。
为什么不能直接用 melody.Default() 做生产推送
melody 默认不设写缓冲、不配心跳超时、没做连接数限制——开发环境跑得欢,压测一上就爆内存或连接堆积。它本质是 gorilla/websocket 的薄封装,不是开箱即用的推送中间件。
-
melody.New()创建的实例默认WriteBufferSize为 0,意味着每次Session.Write()都直写 socket,高并发下容易阻塞写协程 - 心跳只靠
SetPingPeriod发 ping,但没配SetPongTimeout,客户端假死连接会一直挂着不清理 - 所有 session 全局存 map,没做分片或读写锁优化,百万连接时
hub.Broadcast()会成为锁瓶颈
必须重写的三个配置项
初始化时别用裸 New,至少覆盖这三个字段:
-
WriteBufferSize: 1024—— 开启写缓冲,避免单次大消息阻塞整个 session 协程 -
ReadBufferSize: 512—— 小 buffer 减少内存占用,配合SetReadDeadline防粘包阻塞 -
PingPeriod: 30 * time.Second和PongTimeout: 15 * time.Second—— 心跳周期和响应窗口必须成对设,否则静默断连无法感知
示例:
func newMelody() *melody.Melody {
m := melody.New()
m.Config.WriteBufferSize = 1024
m.Config.ReadBufferSize = 512
m.Config.PingPeriod = 30 * time.Second
m.Config.PongTimeout = 15 * time.Second
return m
}
广播性能卡在 hub.Broadcast()?改用分组 channel + worker
原生 hub.Broadcast() 是遍历所有 session 逐个写,连接数过万后延迟飙升。真实场景里,90% 推送都是按 group 或 topic 分发,没必要全量扫。
- 用
sync.Map存map[string][]*melody.Session,按 group ID 索引 session 列表 - 每个 group 起一个独立 goroutine worker,从
chan []byte消费消息,批量写入该 group 所有 session - worker 内部用
session.SetWriteDeadline防止单个慢连接拖垮整组,写失败直接session.Close()
这样既避开 melody hub 的全局锁,又让推送可横向扩展(加 group 就加 worker,不互相干扰)。
goroutine 泄漏最常发生在哪
不是你忘了 defer session.Close(),而是:HandleMessage 回调里启动了长周期 goroutine 却没绑定退出信号。
- 比如在回调里起
time.Ticker给某个 session 定时推数据,但没监听session.Closed()通道 - melody 的 session.Close() 不会自动 cancel 关联 goroutine,必须手动检查
session.IsClosed()或监听session.CloseNotify() - 正确做法:所有长期运行的 goroutine 都要带
select { case
漏掉这一条,每 1000 个连接就可能多出几十个僵尸 goroutine,几天后 OOM。
真正难的不是接通 WebSocket,而是让每个连接在断开瞬间彻底释放资源——melody 把连接生命周期交给你管,它只负责“不崩”,不管“干净”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











