根本问题在于readmessage与业务逻辑共用goroutine;必须拆为读协程(仅readmessage→校验→发channel)和处理协程(反序列化→业务调用),c.recv缓冲区设64,混发大二进制与json时内存易暴增。

gorilla/websocket 里 ReadMessage 阻塞导致消息堆积
根本问题不在“消息太多”,而在于把 ReadMessage 和业务逻辑写在同一个 goroutine 里。一旦 db.Exec 或 http.Post 耗时超过几毫秒,后续消息就卡在 TCP 接收缓冲区,ReadMessage 不返回,连接看似活着,实则已积压数秒甚至数十秒的数据。
必须拆成两个 goroutine:
- 一个只做
ReadMessage→ 校验格式 → 发到c.recv(chan []byte) - 另一个从
c.recv取出、反序列化、调业务逻辑,失败也走重试或死信路径
c.recv 的 buffer 长度建议设为 64:小于 32 容易丢包(尤其突发心跳+业务消息混发),大于 128 会掩盖真实处理瓶颈,还可能让内存占用悄悄翻倍——特别是混着大二进制帧时。
channel 写入不加 select+default 就等于埋雷
很多人直接写 c.send ,但一旦消费者卡住或 channel 满了,这个写操作就会阻塞整个读协程,所有后续 <code>ReadMessage 都被挂起,超时只是时间问题。
正确写法永远带非阻塞保护:
select {
case c.send <p>别指望靠增大 channel 容量来兜底——它解决不了处理慢的本质问题,反而会让积压更隐蔽、更难监控。</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2"><img
src="https://img.php.cn/upload/manual/001/221/864/6a1560869301b447.png" alt="WebSocket 8.18.2" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2" class="overflowclass">WebSocket 8.18.2</a>
<p class="overflowclass">WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。</p>
</div>
<a rel="nofollow" href="/xiazai/gongju/2287" title="WebSocket 8.18.2" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><h3>为什么不能依赖 SetReadDeadline 判断积压</h3><p><code>conn.SetReadDeadline</code> 只管网络层是否收到数据,不管应用层有没有消费。你可能看到连接一直“在线”,<code>ReadMessage</code> 也始终没报错,但 <code>len(c.recv)</code> 已经涨到 50+,<code>lastRecvTime</code> 停在 32 秒前——这说明业务 consumer 卡死了,而 read deadline 完全感知不到。</p><p>真正有用的监控指标是这两个:</p>
-
recvQueueLen:每次往c.recv写之前用len(c.recv)记一次(注意加锁或原子操作) -
lastRecvTime:每次成功写入后更新,配合心跳检测,>30s 无新消息且len(c.recv) > 0就该告警
这些值比 CPU 使用率更能暴露真实瓶颈。
二进制消息 + 大 JSON 混发时的隐性积压
这种组合最危险:它不会触发 i/o timeout,也不会让 ReadMessage 报错,但单条消息可能占几 MB 内存,c.recv 缓冲区里塞 10 条就吃掉上百 MB。压测时只发 "hello" 完全没意义。
上线前必须用真实 payload 压测,至少包括:
- 典型业务 JSON(含嵌套、base64 字段)
- 最大允许尺寸的二进制帧(如图片缩略图、音频片段)
- 混合发送:每 3 条 JSON 夹 1 条二进制
观察 runtime.ReadMemStats 中的 Alloc 和 HeapInuse 增长趋势,比看连接数靠谱得多。










