go_goroutines指标对长连接监控无效,因其是runtime全局计数,混杂各类协程且不区分连接生命周期,无法定位建连还是泄漏;必须用自定义gaugevec按协议、路径、状态打点并确保注册器隔离和线程安全更新。

不能只靠 go_goroutines 默认指标看长连接 Goroutine——它不区分连接生命周期,数值跳变无法定位是正常建连还是泄漏;必须用自定义 GaugeVec 按协议、路径、状态维度打点,并确保注册器隔离和线程安全更新。
为什么 go_goroutines 指标对长连接监控无效
go_goroutines 是 runtime 全局计数,包含 netpoll、timer、sysmon 等内部协程,也混入 HTTP handler、DB 连接池、日志 flusher 等非连接 goroutine。你看到数值从 12 → 846,根本无法判断其中多少来自 WebSocket 握手、gRPC stream 或 http/2 push,更没法知道哪些该释放却没释放。
典型表现:
- 客户端断开后,
go_goroutines居高不下,但ws_clients_total{status="active"}已归零 → 实际是 channel receive 卡住的 goroutine 没退出 - 压测时
go_goroutines冲到 2000,但连接数指标稳定在 500 → 多余 1500 是临时任务 goroutine,不是连接泄漏 - 滚动发布后
go_goroutines趋势图出现阶梯式抬升 → 指标未按实例隔离,多个 pod 的值被 Prometheus 合并抓取
必须用 GaugeVec 按连接状态打点
长连接的生命周期(建立、活跃、异常关闭、超时释放)必须映射为可增可减的瞬时值,CounterVec 只能递增,GaugeVec 才能 .Inc() 建连、.Dec() 断连。
关键实操点:
- 标签设计要收敛:至少含
protocol("ws"/"grpc")、status("active"/"closed")、path(如"/stream/user"),避免用动态 ID(如client_id)导致 series 爆炸 - 注册必须显式绑定到私有注册器:
reg := prometheus.NewRegistry(),再reg.MustRegister(wsConnGauge),绝不用prometheus.DefaultRegisterer - 初始化一次,全局复用:
var wsConnGauge = prometheus.NewGaugeVec(...)放在包级变量,不在 handler 里反复 new - 打点位置要精准:WebSocket 在
conn.SetCloseHandler和conn.Close后调用.Dec();gRPC server streaming 在stream.Context().Done()触发时.Dec(),且需加sync.Once防重复扣减
更新 Goroutine 数必须线程安全且异步
长连接 goroutine 往往在不同 goroutine 中创建/销毁(比如 HTTP handler 启一个,signal handler 清一个),直接调用 .Inc()/.Dec() 会触发 GaugeVec 内部 mutex 竞争,高频建连断连下 /metrics 渲染延迟飙升。
推荐做法:
- 用
sync.Map或atomic.Int64在业务逻辑中维护每个连接维度的 goroutine 计数快照,避免锁 - 后台 goroutine 每 2 秒批量同步到
GaugeVec:wsConnGauge.WithLabelValues("ws", "active", "/stream").Set(float64(activeCount.Load())) - 不要在
http.HandlerFunc或stream.Send中直接.Inc()——这些是 hot path,锁竞争会拖慢整个请求链路 - 验证是否生效:curl
http://localhost:8080/metrics搜索ws_clients_total,确认 label 值正确、数值随连接变化
Prometheus 抓取配置与告警要匹配长连接特性
默认 15s 抓取间隔对长连接太慢:goroutine 泄漏可能在 3 秒内完成(比如未设 timeout 的 http.Client + channel receive),等 Prometheus 下次 scrape 时已 OOM。
必须调整:
- Prometheus 配置中把 job 的
scrape_interval设为3s,并配scrape_timeout: 2s防超时中断 - 告警规则不能写
go_goroutines > 1000—— 它不反映连接状态;应写ws_clients_total{status="active"} > 500 and ws_clients_total{status="active"} > (ws_clients_total{status="active"} offset 1m) - 容器部署时,确保
livenessProbe不用/metrics:该端点若因 gauge 更新卡顿,会触发误重启 - 若用 Kubernetes ServiceMonitor,
sampleLimit必须 ≥ 10000,否则大量 label 组合会被截断,导致部分连接数丢失
最易被忽略的一点:所有连接指标的 status 标签必须覆盖“异常关闭”场景(如网络中断、心跳超时),而不仅是 conn.Close() 正常路径——90% 的泄漏发生在异常分支未打点。别只测 happy path。











