runtime/metrics不能直接当prometheus客户端用,因为它仅提供一次性只读快照,无http端点、无自动注册机制、无标签支持,需手动定时采集、差值计算、维度补全并转为prometheus格式。

runtime/metrics 为什么不能直接当 Prometheus 客户端用
它不是暴露 HTTP 端点的监控服务,而是 Go 运行时指标的只读快照接口。你拿不到持续流式数据,也不能直接注册到 prometheus.NewRegistry()。想对接 Prometheus,必须自己定时采集 + 转换,否则指标就“一闪而过”。
- 所有指标都通过
runtime/metrics.Read一次性读取,返回的是[]metrics.Sample,不是通道或回调 - 指标名称是固定字符串(如
/gc/heap/allocs:bytes),没有标签(label),需手动补维度(如服务名、实例 ID) - 部分指标是累计值(如
/gc/heap/allocs:bytes),需做差值计算才能得到速率;另一些是瞬时值(如/memory/classes/heap/released:bytes),可直接上报
如何安全地每秒采集一次并避免阻塞 GC
runtime/metrics.Read 内部会暂停世界(stop-the-world)极短时间,但高频调用仍可能干扰调度。生产环境建议 1–5 秒间隔,且必须在单独 goroutine 中执行,绝不能塞进 HTTP handler 或关键路径。
- 用
time.Ticker控制频率,别用time.Sleep配合 for 循环——后者误差大、易漂移 - 采集前加超时控制:传入带 deadline 的
context.Context,防止因 runtime 锁异常卡死 - 采集结果应先写入无锁 ring buffer(如
github.com/cespare/xxhash/v2+ slice 复用),再批量转成 Prometheus 格式,避免每次采集都分配对象
示例节选:
go func() {
ticker := time.NewTicker(3 * time.Second)
defer ticker.Stop()
for range ticker.C {
samples := make([]metrics.Sample, len(allKeys))
for i := range samples {
samples[i].Name = allKeys[i]
}
if err := metrics.Read(samples); err != nil {
log.Warn("failed to read runtime metrics", "err", err)
continue
}
pushToPrometheus(samples) // 自定义转换+上报逻辑
}
}()
哪些指标最值得监控,以及它们的单位和含义差异
Go 1.21+ 指标命名统一为 /category/subcategory/name:unit,但单位容易误读。比如 :bytes 是字节数,:objects 是对象个数,:nanoseconds 是纳秒——千万别当成毫秒除以 1000。
-
/gc/heap/allocs:bytes:累计分配字节数,用于计算每秒分配率(需 diff) -
/gc/heap/objects:objects:当前堆上存活对象数,突增可能预示内存泄漏 -
/memory/classes/heap/unused:bytes:已向 OS 申请但未被 Go 使用的内存,持续高值说明内存未及时返还 -
/sched/goroutines:goroutines:当前活跃 goroutine 数,>10k 且持续上升需警惕协程泄漏 -
/gc/pauses:seconds:GC 暂停时间直方图,注意看 99% 分位值,不是平均值
与 pprof 和 expvar 的关键区别在哪
runtime/metrics 是轻量级、低开销、结构化指标源;pprof 是诊断性采样工具(CPU/memory/block),开销高、不适用长期监控;expvar 是简单键值暴露,类型弱、无单位、不支持直方图。
- 上线后要长期跑的指标(如每秒 GC 暂停时长、堆增长速率),必须用
runtime/metrics - 排查某次慢请求?用
net/http/pprof抓 30s CPU profile - 临时看某个计数器?
expvar.NewInt("req_count").Add(1)更快,但没法和 GC 指标对齐时间轴 - 三者可以共存,但别把
pprof的 /debug/pprof/mutex 暴露到公网——这是安全风险
真正麻烦的是直方图指标(如 /gc/pauses:seconds)的解析:它返回的是分桶后的 float64 切片,不是原始分布,聚合逻辑得自己写清楚,不然告警阈值会失真。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











