
一切看起来都很正常。页面没报错,服务没崩,CPU稳得像装饰品,用户没明确投诉……直到你翻开日志,看见一串刺眼的消息:
csharp[warn] WebSocket 心跳超时,准备断开连接...
你调了下 Prometheus 图表一看——心跳失败率,暴涨。曲线像电锯,20分钟从1%冲到了32%,高点直插100%。你心里立马蹦出一句话:“完了。”
WebSocket 心跳机制,是所有实时系统的底线。你不监控它,用户断线你都不知道;你监控不精准,它出问题了你也不知道到底是哪端错了。
所以今天,我们要聊的不是“什么是心跳”,而是当它失败率暴增时,你要如何实时发现、自动响应、精准定位、迅速恢复。这不是文档能教你的,这是战场才能练出来的。
这不是“断线”,这是“诈尸”
WebSocket 最诡异的一点是:**它断了,但看起来还连着。**你如果只看连接状态,它还是 OPEN;你如果不做心跳,它就像个死人坐在那不动,你也不知道他是不是睡着了。直到用户开始疯狂刷新页面、重连失败、错过推送,你才意识到:它,其实早断了。
为什么心跳失败率会飙升?
- 移动端系统切后台被“休眠”
- 网络抖动导致 ping 或 pong 丢失
- 代理或 CDN 超时断开但客户端不感知
- 服务器 event loop 忙不过来处理心跳
- 客户端心跳逻辑写得太“理想化”
这不是代码问题,是你系统对“假连接”无感知。
心跳机制 ≠ setInterval(ping)
真正有效的 WebSocket 心跳机制设计需要具备三个要素:
- 主动发起 ping
- 被动响应 pong
- 失败判断 + 自愈机制
示例代码:
jslet missedHeartbeats = 0;
const MAX_MISSES = 3;
const PING_INTERVAL = 10000;
function heartbeat() {
if (missedHeartbeats >= MAX_MISSES) {
socket.close();
reconnect();
return;
}
missedHeartbeats++;
socket.send(JSON.stringify({ type: "ping" }));
}
socket.onmessage = function(event) {
const msg = JSON.parse(event.data);
if (msg.type === "pong") {
missedHeartbeats = 0;
}
};
setInterval(heartbeat, PING_INTERVAL);
如何精准“量化”失败率:靠 Prometheus,而不是靠肉眼
别再靠 ELK 翻日志,太慢太滞后,用户早跑了你还在 grep “timeout”。
正确做法:埋点 + 上报指标
prometheuswebsocket_heartbeat_ping_total
websocket_heartbeat_pong_received_total
websocket_heartbeat_failed_total
PromQL:
promql(
rate(websocket_heartbeat_failed_total[1m])
/
rate(websocket_heartbeat_ping_total[1m])
) * 100
一键部署 Alertmanager 告警规则
yamlgroups:
- name: websocket-heartbeat
rules:
- alert: WebSocketHeartbeatFailureSpike
expr: (
rate(websocket_heartbeat_failed_total[1m])
/
rate(websocket_heartbeat_ping_total[1m])
) > 0.1
for: 2m
labels:
severity: critical
annotations:
summary: "WebSocket 心跳失败率暴涨"
description: "过去2分钟内 WebSocket 心跳失败率持续高于10%,可能已出现大面积断链"
客户端自动重连 ≠ 一味重连
用指数退避:
jslet retry = 0;
function reconnect() {
const delay = Math.min(1000 * 2 ** retry, 30000);
setTimeout(() => {
initSocket();
retry++;
}, delay);
}
中间设备如何误杀你的连接
层级 | 问题 |
---|---|
CDN | 60s无数据断链 |
SLB | TCP空闲超时 |
Nginx | proxy_read_timeout 过低 |
Firewall | 心跳被拦截 |
应对方式:
- 客户端 20~30s 定时 ping
- 服务端设置 keepalive_timeout 合理值
- 与网络团队确认 CDN 保活策略
Grafana 图形化建议
- 失败率趋势折线图
- 失败率按设备分类柱状图
- 地区失败率热力图
- 报警历史趋势图
- 正常 vs 异常连接数对比
高阶技巧:用 eBPF 实时抓断链细节
用 bcc:
bashsudo ./tcplife -p <pid>
你能看到 socket 的存活时间、断链原因、reset 事件,辅助诊断“连接为啥死了”。
心跳不是为了你写了它,是为了你能“感知断链”
WebSocket 最怕的是它断了你却看不见。你需要的不是“写一个 ping”,而是:
- 失败率可量化
- 告警机制健全
- 自动重连可控
- 原因诊断清晰
否则你的系统就像带病工作的人,看上去还活着,实际上心跳已经紊乱。