3. Exporter 开发基础:使用 Prometheus 客户端库暴露自定义指标

在生产环境中,除了使用官方 Exporter(如 Node Exporter、Blackbox Exporter)采集标准指标外,SRE 团队经常需要为自研中间件、业务逻辑或基础设施组件开发自定义 Exporter。本章将讲解如何使用 Prometheus 官方客户端库(以 Go 语言为例)快速构建一个 Exporter,并暴露自定义指标。

3.1 Exporter 的核心工作模式

一个标准的 Prometheus Exporter 遵循“拉取(Pull)”模型,其内部结构通常包含三个核心阶段:指标定义、数据采集、HTTP 暴露。下图展示了 Exporter 与 Prometheus Server 之间的交互流程:

Exporter 核心工作模式 Exporter 核心工作模式:指标定义 → 采集 → 暴露 Prometheus Server HTTP Handler Collector 逻辑 目标系统/API HTTP GET /metrics 调用 Collect() 查询状态 说明:Prometheus 定期向 Exporter 的 /metrics 端点发起 HTTP 请求,Exporter 实时采集目标数据并返回指标文本。

3.2 使用 Prometheus 客户端库(Go 示例)

Prometheus 官方为多种语言提供了客户端库,包括 Go、Java、Python、Ruby 等。Go 客户端库是最成熟、性能最高的选择,也是开发自定义 Exporter 的首选。以下步骤演示如何从零构建一个 Exporter。

3.2.1 初始化项目与引入依赖

# 初始化 Go 模块
go mod init myapp-exporter

# 安装 Prometheus 客户端库
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp

3.2.2 定义自定义指标

在 Go 代码中,使用 prometheus.NewGaugeVec、prometheus.NewCounterVec 或 prometheus.NewHistogramVec 定义指标。每个指标需要指定 Metric Name、Help 文本 和 Label 名称。

package main

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

// 定义一个 Gauge 指标:当前活跃连接数,带 service 和 region 标签
var activeConnections = promauto.NewGaugeVec(
    prometheus.GaugeOpts{
        Name: "myapp_active_connections_total",
        Help: "Current number of active connections by service and region.",
    },
    []string{"service", "region"},
)

// 定义一个 Counter 指标:累计请求数,带 status 标签
var requestCount = promauto.NewCounterVec(
    prometheus.CounterOpts{
        Name: "myapp_requests_total",
        Help: "Total number of HTTP requests processed.",
    },
    []string{"status"},
)

// 定义一个 Histogram 指标:请求延迟分布
var requestDuration = promauto.NewHistogramVec(
    prometheus.HistogramOpts{
        Name:    "myapp_request_duration_seconds",
        Help:    "Histogram of request durations in seconds.",
        Buckets: prometheus.DefBuckets, // 默认桶: .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10
    },
    []string{"method"},
)
命名规范:自定义指标名称应遵循 namespace_metricname_unit 格式,例如 myapp_active_connections_total。避免使用下划线以外的特殊字符,且不要以数字开头。

3.2.3 实现采集逻辑与注册

在业务代码中更新指标值,并通过 prometheus.MustRegister() 或 promauto 自动注册到默认注册表中。

func recordMetrics() {
    // 模拟采集数据并设置指标值
    activeConnections.With(prometheus.Labels{"service": "api", "region": "us-east-1"}).Set(42)
    activeConnections.With(prometheus.Labels{"service": "web", "region": "eu-west-1"}).Set(18)

    requestCount.With(prometheus.Labels{"status": "200"}).Add(1024)
    requestCount.With(prometheus.Labels{"status": "500"}).Add(3)

    requestDuration.With(prometheus.Labels{"method": "GET"}).Observe(0.125)
    requestDuration.With(prometheus.Labels{"method": "POST"}).Observe(0.350)
}

func main() {
    // 启动一个 goroutine 定期采集(示例中仅执行一次)
    recordMetrics()

    // 暴露 /metrics 端点
    http.Handle("/metrics", promhttp.Handler())
    log.Fatal(http.ListenAndServe(":8080", nil))
}

3.3 指标类型与适用场景对比

选择合适的指标类型是 Exporter 开发的关键。下表总结了四种核心指标类型的特性与典型用途:

指标类型 数据类型 典型场景 可加减 示例
Counter 只增不减 请求总数、错误次数、任务完成数 仅加法 http_requests_total
Gauge 可增可减 内存使用量、连接数、温度 加减均可 node_memory_usage_bytes
Histogram 分布统计 请求延迟、响应大小 分桶计数 http_request_duration_seconds
Summary 分位数统计 延迟分位数(如 p99) 客户端计算 rpc_duration_seconds
避坑提示:不要将 Counter 用于可下降的指标(如当前连接数),否则 PromQL 的 rate() 函数会产生错误结果。Gauge 类型不要使用 rate(),应使用 avg_over_time() 或 delta()。

3.4 标签(Labels)设计原则

标签是 Prometheus 多维数据模型的核心。设计不当的标签会导致基数爆炸(Cardinality Explosion),严重影响 Prometheus 性能。请遵循以下原则:

  • 限制标签值数量:每个标签的取值数量应控制在 100 以内。避免将用户 ID、请求 IP、Email 等无限增长的字段作为标签。
  • 使用有意义的标签名:如 status、method、endpoint、region,避免使用 type1、attr2 等模糊名称。
  • 标签顺序一致:同一指标的不同标签组合,在代码中保持固定的标签顺序,否则会导致指标重复注册。
  • 避免冗余标签:如果两个标签总是成对出现且一一对应,考虑合并为一个标签。

3.5 启动 Exporter 并验证

编译并运行上述代码后,Exporter 会在 localhost:8080 监听。使用 curl 或浏览器访问 /metrics 端点,即可看到自定义指标的输出:

# 启动 Exporter
go run main.go

# 在另一个终端验证指标
curl http://localhost:8080/metrics

# 输出示例(部分):
# HELP myapp_active_connections_total Current number of active connections by service and region.
# TYPE myapp_active_connections_total gauge
myapp_active_connections_total{region="us-east-1",service="api"} 42
myapp_active_connections_total{region="eu-west-1",service="web"} 18
# HELP myapp_requests_total Total number of HTTP requests processed.
# TYPE myapp_requests_total counter
myapp_requests_total{status="200"} 1024
myapp_requests_total{status="500"} 3
生产建议:在生产环境中,Exporter 应作为独立的二进制进程运行,并通过 systemd 或容器编排工具(如 Kubernetes Deployment)管理。建议为 Exporter 添加健康检查端点(如 /health)和日志输出,便于故障排查。

3.6 本章小结