3. Exporter 开发基础:使用 Prometheus 客户端库暴露自定义指标
在生产环境中,除了使用官方 Exporter(如 Node Exporter、Blackbox Exporter)采集标准指标外,SRE 团队经常需要为自研中间件、业务逻辑或基础设施组件开发自定义 Exporter。本章将讲解如何使用 Prometheus 官方客户端库(以 Go 语言为例)快速构建一个 Exporter,并暴露自定义指标。
3.1 Exporter 的核心工作模式
一个标准的 Prometheus Exporter 遵循“拉取(Pull)”模型,其内部结构通常包含三个核心阶段:指标定义、数据采集、HTTP 暴露。下图展示了 Exporter 与 Prometheus Server 之间的交互流程:
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 |
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
/health)和日志输出,便于故障排查。