4. 指标采集与暴露:Prometheus客户端接入、Histogram/Summary/Gauge/Counter使用场景、/metrics端点设计

好,咱们进入正题。报价路由系统里,指标怎么采、怎么露,这事儿我踩过的坑可不少。说白了,Prometheus 客户端接入本身不难,难的是选对指标类型,以及把 /metrics 端点设计得既清晰又高效。

4.1 Prometheus 客户端接入:别小看这一步

接入 Prometheus 客户端,我习惯用官方的 client_golang(如果服务是 Go 写的)。Java 项目就用 simpleclient。嗯,这里要注意:版本一定要对齐,不然会有兼容性问题。

我曾经在一个老项目里,直接 copy 了网上的一段代码,结果 client 版本和 Prometheus server 版本不匹配,指标一直报错。排查了半天,最后发现是 protobuf 格式不一致。所以,我建议你直接从官方仓库拉依赖,别偷懒。

接入的核心就两步:

  • 引入依赖:在项目中添加 Prometheus 客户端库。
  • 注册指标:创建并注册你需要的指标对象。

举个例子,Go 语言里这样搞:

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
    // 定义一个 Counter 指标
    requestCount = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "route_requests_total",
            Help: "Total number of routing requests.",
        },
        []string{"method", "endpoint"},
    )
)

func init() {
    // 注册到默认注册器
    prometheus.MustRegister(requestCount)
}

你看,就这么简单。但别忘了,MustRegister 如果注册重复的指标会 panic。我在生产环境见过因为这个 panic 导致服务重启的案例。所以,我建议你注册前先检查一下,或者用 Register 方法处理错误。

4.2 四种指标类型:选对工具干对活

Prometheus 提供了四种核心指标类型:Counter、Gauge、Histogram、Summary。很多新手容易搞混,我一个个说。

4.2.1 Counter:只增不减的计数器

Counter 就是计数器,只能增加,不能减少。适合统计请求总数、错误次数、任务完成数等。

举个例子,报价路由系统里,我们统计“总路由请求数”:

requestCount.With(prometheus.Labels{"method": "GET", "endpoint": "/quote"}).Inc()

为什么用 Counter?因为它的值只增不减,Prometheus 可以计算速率(rate),比如“每秒请求数”。

我的经验:Counter 的初始值一定是 0。如果你需要从非零值开始,可以用 Add() 方法。但说实话,我很少这么干,因为 Prometheus 更关心增量变化。

4.2.2 Gauge:可增可减的仪表盘

Gauge 可以上下波动,适合内存使用率、CPU 负载、队列长度、在线连接数等。

在报价路由系统里,我们监控“当前待处理报价数”:

pendingQuotes.Set(float64(len(queue)))

Gauge 的典型用法是 Set()Inc()Dec()。我建议你尽量用 Set(),因为它能保证数值的准确性。用 Inc()/Dec() 在高并发下可能会有竞态问题,虽然 Prometheus 客户端内部做了原子操作,但逻辑上还是 Set() 更可控。

注意:Gauge 不适合统计“速率”或“趋势”,它只反映当前快照。如果你想看变化趋势,还是得用 Counter 配合 rate() 函数。

4.2.3 Histogram:分布直方图

Histogram 用来统计数据的分布情况,比如请求延迟、响应大小。它会将数据分桶(bucket),然后统计每个桶里的样本数。

举个例子,我们统计“报价路由请求延迟”:

requestDuration = prometheus.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "route_request_duration_seconds",
        Help:    "Histogram of routing request durations.",
        Buckets: prometheus.DefBuckets, // 默认桶:.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10
    },
    []string{"method"},
)

使用的时候:

timer := prometheus.NewTimer(requestDuration.WithLabelValues("GET"))
defer timer.ObserveDuration()

Histogram 能告诉你:有多少请求在 100ms 以内?有多少在 500ms 以内?这对于设置 SLO(服务等级目标)非常有用。

我个人习惯把桶设置得跟业务相关。比如报价路由要求 P99 在 200ms 以内,那我就会在 200ms 附近多设几个桶:

Buckets: []float64{0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.5, 1.0}

4.2.4 Summary:预计算的分位数

Summary 和 Histogram 类似,也是统计分布。但 Summary 在客户端就计算好了分位数(quantile),直接暴露给 Prometheus。

举个例子:

requestDurationSummary = prometheus.NewSummaryVec(
    prometheus.SummaryOpts{
        Name:       "route_request_duration_summary_seconds",
        Help:       "Summary of routing request durations.",
        Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
    },
    []string{"method"},
)

这里 Objectives 定义了你要计算的分位数,以及允许的误差。比如 0.99: 0.001 表示 P99 的误差在 0.1% 以内。

核心区别:
  • Histogram 在服务端计算分位数,可以灵活聚合。
  • Summary 在客户端计算,无法跨进程聚合。

我建议:如果你需要跨多个实例计算全局 P99,用 Histogram。如果只关心单个实例的延迟,用 Summary 更省资源。

4.3 /metrics 端点设计:别一股脑全暴露

/metrics 端点就是 Prometheus 来抓取数据的地方。设计得好,运维省心;设计得不好,Prometheus 抓取超时,甚至把服务拖垮。

我见过最离谱的 /metrics 端点,把每个请求的完整日志都暴露出来了,结果 Prometheus 一抓,内存直接爆了。嗯,这绝对是反面教材。

设计 /metrics 端点,我总结了几个原则:

  1. 只暴露必要的指标:不要把所有内部变量都暴露出去。比如,不要暴露每个用户的详细数据,只暴露聚合后的统计值。
  2. 控制指标数量:一个服务暴露几百个指标很正常,但几千个就有点过了。我建议单个服务不超过 1000 个时间序列。
  3. 使用标签(Labels)要克制:标签是 Prometheus 的维度,但标签值太多会导致基数爆炸。比如,不要把用户 ID 作为标签,因为用户数量可能上百万。
  4. 设置抓取超时:在 Prometheus 配置里设置 scrape_timeout,比如 10 秒。如果 /metrics 生成太慢,就优化代码。

下面是一个标准的 /metrics 端点实现(Go 语言):

http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":8080", nil))

就这么一行。但如果你需要自定义,比如加个认证或过滤,可以自己包装一下:

func metricsHandler() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 可以加一些鉴权逻辑
        promhttp.Handler().ServeHTTP(w, r)
    })
}

4.4 知识体系图:指标采集与暴露的核心逻辑

下面这张图,是我自己总结的。它把整个流程串起来了,你一看就明白。

指标采集与暴露核心逻辑 应用代码 业务逻辑执行 指标注册 Counter/Gauge/Histogram/Summary /metrics 端点 HTTP 暴露 Prometheus 抓取 scrape_interval / timeout 四种指标类型详解 Counter 只增不减,适合计数 Gauge 可增可减,适合快照 Histogram 分桶统计,适合延迟 Summary 预计算分位数 💡 核心原则:标签基数要控制,指标数量要精简,抓取超时要设置

4.5 避坑指南:我踩过的那些坑

最后,分享几个我亲身经历过的坑,希望能帮你少走弯路。

  • 标签基数爆炸:我曾经把一个请求的 trace ID 作为标签,结果 Prometheus 存储直接爆了。后来改成只统计聚合值,问题解决。
  • Histogram 桶设置不合理:默认桶最大是 10 秒,但我们的报价路由延迟都在毫秒级,导致所有请求都落在第一个桶里。后来我根据业务重新设置了桶范围。
  • /metrics 端点没有限流:有一次 Prometheus 抓取频率太高,加上 /metrics 生成慢,导致服务响应超时。我后来加了限流和缓存,才稳住。
  • Summary 无法聚合:我一开始用 Summary 统计全局 P99,结果发现每个实例的 P99 都不一样,无法直接求平均。后来换成 Histogram,用 histogram_quantile 函数在 PromQL 里计算,才得到准确结果。

嗯,这些坑说起来都是泪。但吃一堑长一智,现在我做指标设计,都会先画个图,想清楚再动手。


公众号:蓝海资料掘金营,微信deep3321