prometheus.newgaugevec是统计在线连接数的正确选择,因其支持瞬时增减的gauge类型及多维标签(如protocol、instance),避免cardinality爆炸和指标混淆,需配合原子操作、缓存快照与自定义collector确保线程安全与抓取稳定。

为什么 prometheus.NewGaugeVec 是统计在线连接数的正确选择
因为在线客户端数量是瞬时、可增可减的指标,必须用 Gauge 类型,不能用 Counter(只增不减)或 Histogram(用于分布统计)。用 NewGaugeVec 而不是 NewGauge,是为了支持按协议类型(如 ws / tcp)、服务实例(instance_id)等维度打点,后续在 Prometheus 查询时才能灵活 sum by (protocol) 或 count by (job)。
常见错误是直接注册一个无标签的 Gauge,结果多个服务实例上报冲突,或者不同连接类型混在一起无法区分。
- 标签名必须提前定义好,比如
[]string{"protocol", "instance"},注册后不可更改 - 初始化时调用
gauge.WithLabelValues("ws", "srv-01")获取子指标,每次增减都作用于该组合 - 避免在连接建立/关闭时反复调用
WithLabelValues—— 它会创建新指标,导致 cardinality 爆炸
长连接生命周期中如何安全增减 Gauge 值
关键在于:增减操作必须与连接的实际状态严格对齐,且线程安全。Golang 的 net/http 或 gorilla/websocket 通常在 goroutine 中处理单个连接,但连接关闭可能由任意方触发(客户端断开、心跳超时、服务重启),所以增减逻辑要放在「确定连接已终止」的位置,而不是连接建立成功就立刻 +1。
典型陷阱是:在 http.HandlerFunc 里调用 gauge.Inc() 后,没做 defer 减值,或 defer 放在了 websocket upgrade 之前,导致连接失败但计数已+1。
- WebSocket 场景:在
conn.ReadMessage()返回io.EOF或websocket.CloseMessage后,再执行gauge.WithLabelValues("ws", instanceID).Dec() - TCP 长连接:在
conn.Read返回io.EOF或err != nil且!net.ErrClosed时减 1;连接建立成功后立即Inc() - 务必用
sync.Once或原子操作包裹首次增减,防止同一连接被多次计数(例如重连未清理旧 goroutine)
如何让 Prometheus 正确抓取并避免 context deadline exceeded
Prometheus 默认每 15s 抓一次 /metrics,而长连接服务如果在 handler 里遍历所有活跃连接再拼字符串,容易阻塞 HTTP handler,导致抓取超时。不要在 http.Handler 里实时计算连接数 —— 应该把当前值缓存在内存中,handler 只读取快照。
错误做法:func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "online_clients %d", len(activeConns)) } —— len(activeConns) 看似快,但若 activeConns 是 map 且无锁,可能 panic;若加锁又拖慢所有请求。
- 用
atomic.Int64或sync.Map存储每个标签组合的当前值,在连接增减时同步更新 - 注册自定义 collector:实现
prometheus.Collector接口,Collect()方法只 copy 当前值到prometheus.Metric,不查 live 数据结构 - 确保
http.ListenAndServe绑定的 metrics endpoint 不和业务端口共用 TLS 配置异常(比如 client auth 强制开启),否则抓取会因证书问题失败
调试时怎么看实际暴露的指标是否准确
直接访问 /metrics 端点最可靠。注意看三件事:指标名是否带 _total(那是 Counter,错了)、是否有你定义的标签(如 {protocol="ws",instance="local"})、数值是否随连接开关实时变化。别只信 Grafana 图表 —— 它可能开了缓存或聚合。
常见干扰项:go_goroutines 或 process_open_fds 数值接近在线连接数,让人误以为对了,其实只是巧合。真正属于你的指标应该以你注册时指定的 name 开头,比如 app_online_clients。
- curl -s http://localhost:8080/metrics | grep online_clients
- 连接一个客户端后立刻再 curl,确认数值 +1;关闭后确认 -1;重复 2 次,检查是否回到 0
- 如果指标一直为 0,检查
gauge.WithLabelValues(...).Inc()是否被调用(加日志或断点),以及该 label 组合是否和Describe()中声明的一致
最易忽略的是:Prometheus server 的 scrape_configs 里 job_name 和你代码里 Register 的 registry 是否属于同一个实例 —— 多个 registry 未合并会导致指标不上报。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











