Prometheus源码专题【左扬精讲】—— Prometheus Exporter 定制化开发:架构设计篇
Prometheus源码专题【左扬精讲】—— Prometheus Exporter 定制化开发:架构设计篇
在 Prometheus 监控体系中,Exporter 作为指标采集与暴露的核心组件,其架构设计直接影响监控系统的灵活性、可扩展性和可靠性。本文将以 Go 语言客户端为例,从架构设计层面解析 Prometheus Exporter 的核心组件与工作原理,重点探讨汇集器(Gatherer)、采集器(Collector)的协作模式,以及多汇集器架构的设计思路。
一、Exporter 核心架构概览
Prometheus Exporter 的核心职责是采集目标系统的指标数据并通过 HTTP 接口暴露给 Prometheus Server。在 Go 语言客户端(https://github.com/prometheus/client_golang)中,这一过程通过一套清晰的组件分层实现:
-
- 采集器(Collector):指标数据的生产者,负责从特定数据源(如应用内状态、外部 API、日志文件等)采集原始指标,并按照 Prometheus 数据模型进行格式化。
- 汇集器(Gatherer):指标数据的汇总者,接收来自多个 Collector 的指标样本,进行校验、去重、排序后,转换为 Prometheus 协议兼容的 protobuf 格式。
- HTTP 服务:指标数据的暴露者,将 Gatherer 处理后的 protobuf 数据编码为文本或二进制格式,通过/metrics端点响应 Prometheus 的拉取请求。
三者的协作流程可概括为:Collector 生成样本 → 写入 Gatherer 的通道 → Gatherer 汇总处理 → 转换为 HTTP 响应。这种分层设计使数据采集与数据处理解耦,为定制化开发提供了灵活的扩展点。
二、单个汇集器构成的 Exporter

单个 Gatherer 架构是 Exporter 的基础形态,适用于大多数简单场景。其核心特点是通过一个 Gatherer 实例管理所有 Collector,形成 "单调度中心" 模式。
- Registry:作为 Collector 的注册中心,提供MustRegister等方法管理 Collector 实例,确保 Gatherer 能统一发现所有采集器。
- 默认 Gatherer:Registry 默认实现 Gatherer 接口,当调用handler.Handler()时,底层通过 Registry 的Gather方法触发指标汇总。
- 开发者定义多个 Collector,分别采集不同类型的指标(如系统 CPU、内存、业务接口 QPS 等)。
- 通过prometheus.MustRegister将所有 Collector 注册到默认 Registry。
- 启动 HTTP 服务,当 Prometheus 请求/metrics时,Registry(作为 Gatherer)调用所有 Collector 的Collect方法,收集指标样本。
- Gatherer 对样本进行校验(如指标名格式、标签合法性)、去重(相同指标 + 标签组合仅保留最新值)和排序,生成 protobuf 格式的MetricFamily列表。
- HTTP 处理器将 protobuf 数据编码为文本格式(默认),返回给 Prometheus。
三、多个汇集器构成的 Exporter

当监控场景复杂化(如需要从多个独立系统采集指标、或部分指标需要特殊处理逻辑)时,多 Gatherer 架构成为更优选择。其核心设计是通过prometheus.Gatherers(复数形式)聚合多个独立 Gatherer 实例,实现指标的分层汇总。
1. 架构特点
- 独立 Gatherer 实例:每个 Gatherer 可关联不同的 Collector 集合,例如:
- Gatherer A:负责采集主机硬件指标(CPU、磁盘 IO)。
- Gatherer B:负责采集应用业务指标(订单量、支付成功率)。
- Gatherer C:负责采集第三方服务调用指标(API 响应时间、错误率)。
- Gatherers 集合:通过prometheus.Gatherers{A, B, C}将多个 Gatherer 打包,其Gather方法会按顺序调用每个 Gatherer 的Gather方法,合并所有指标结果。
2. 设计优势
- 隔离性:不同 Gatherer 管理的指标相互独立,某类指标采集失败(如第三方 API 超时)不会影响其他 Gatherer 的正常工作。
- 扩展性:可根据业务需求动态增减 Gatherer 实例,无需修改已有采集逻辑。
- 定制化处理:针对特殊指标(如敏感数据),可在独立 Gatherer 中实现专属的过滤或转换逻辑。
例如,在微服务架构中,一个 Exporter 可通过多 Gatherer 分别采集服务自身指标、依赖的数据库指标和消息队列指标,各 Gatherer 可独立配置超时时间和重试策略。
四、Registry、Gatherers 与 GathererFunc 解析
这三个概念是理解 Exporter 架构的核心,它们从不同层面支撑了指标的注册、汇总与扩展能力。
4.1、Registry:Collector 的管理中枢
https://github.com/prometheus/client_golang/blob/v1.22.0/prometheus/registry.go#L96
核心作用:维护 Collector 的生命周期,提供注册、反注册接口,确保 Gatherer 能高效发现所有采集器。
实现特性:
- 内置DefaultRegisterer作为全局默认注册中心,简化简单场景的开发。
- 支持NewRegistry创建独立 Registry,实现多组 Collector 的隔离管理(例如生产环境与测试环境的指标分离)。
- 当注册重复的 Collector(同一指标名且标签规则冲突)时,会触发 panic,避免指标混乱。
Registry 的设计体现了依赖注入思想,通过统一注册机制解耦 Gatherer 与 Collector 的直接关联,使 Gatherer 无需关心具体的采集逻辑。
4.2、Gatherers:多 Gatherer 的聚合器
https://github.com/prometheus/client_golang/blob/v1.22.0/prometheus/registry.go#L140
本质:Gatherers是一个实现了 Gatherer 接口的切片类型,可以将多个不同来源的指标聚合在一起,统一收集。
4.2.1、Gatherer 接口定义
// Gatherer is the interface for the part of a registry in charge of gathering
// the collected metrics into a number of MetricFamilies. The Gatherer interface
// comes with the same general implication as described for the Registerer
// interface.
type Gatherer interface {
// Gather calls the Collect method of the registered Collectors and then
// gathers the collected metrics into a lexicographically sorted slice
// of uniquely named MetricFamily protobufs. Gather ensures that the
// returned slice is valid and self-consistent so that it can be used
// for valid exposition. As an exception to the strict consistency
// requirements described for metric.Desc, Gather will tolerate
// different sets of label names for metrics of the same metric family.
//
// Even if an error occurs, Gather attempts to gather as many metrics as
// possible. Hence, if a non-nil error is returned, the returned
// MetricFamily slice could be nil (in case of a fatal error that
// prevented any meaningful metric collection) or contain a number of
// MetricFamily protobufs, some of which might be incomplete, and some
// might be missing altogether. The returned error (which might be a
// MultiError) explains the details. Note that this is mostly useful for
// debugging purposes. If the gathered protobufs are to be used for
// exposition in actual monitoring, it is almost always better to not
// expose an incomplete result and instead disregard the returned
// MetricFamily protobufs in case the returned error is non-nil.
Gather() ([]*dto.MetricFamily, error)
}
任何实现了 Gather() 方法的类型都可以作为 Gatherer。
4.2.2、Gatherers 类型定义
Gatherers 的定义如下(https://github.com/prometheus/client_golang/blob/v1.22.0/prometheus/registry.go#L743):
// Gatherers is a slice of Gatherer instances that implements the Gatherer
// interface itself. Its Gather method calls Gather on all Gatherers in the
// slice in order and returns the merged results. Errors returned from the
// Gather calls are all returned in a flattened MultiError. Duplicate and
// inconsistent Metrics are skipped (first occurrence in slice order wins) and
// reported in the returned error.
//
// Gatherers can be used to merge the Gather results from multiple
// Registries. It also provides a way to directly inject existing MetricFamily
// protobufs into the gathering by creating a custom Gatherer with a Gather
// method that simply returns the existing MetricFamily protobufs. Note that no
// registration is involved (in contrast to Collector registration), so
// obviously registration-time checks cannot happen. Any inconsistencies between
// the gathered MetricFamilies are reported as errors by the Gather method, and
// inconsistent Metrics are dropped. Invalid parts of the MetricFamilies
// (e.g. syntactically invalid metric or label names) will go undetected.
// Gatherers 是一个 Gatherer 实例的切片,它自身实现了 Gatherer 接口。
// 其 Gather 方法会按顺序调用切片中所有 Gatherer 的 Gather 方法,并返回合并后的结果。
// 从 Gather 调用返回的错误将以扁平化的 MultiError 形式返回。
// 重复和不一致的指标将被跳过(切片顺序中的首次出现优先),并在返回的错误中报告。
//
// Gatherers 可用于合并多个注册表的 Gather 结果。
// 它还提供了一种方法,通过创建一个自定义的 Gatherer,其 Gather 方法直接返回现有的 MetricFamily 协议缓冲区,
// 从而将现有的 MetricFamily 协议缓冲区直接注入到收集过程中。
// 请注意,这里不涉及注册(与 Collector 注册不同),因此显然无法进行注册时的检查。
// Gather 方法会将收集到的 MetricFamily 之间的任何不一致情况报告为错误,并丢弃不一致的指标。
// MetricFamily 的无效部分(例如语法无效的指标或标签名称)将无法被检测到。
type Gatherers []Gatherer
// Gather implements Gatherer.
// Gather 方法实现了 Gatherer 接口
func (gs Gatherers) Gather() ([]*dto.MetricFamily, error) {
// 用于存储按名称分组的 MetricFamily
var (
metricFamiliesByName = map[string]*dto.MetricFamily{}
// 用于存储指标的哈希值,用于去重
metricHashes = map[uint64]struct{}{}
// 收集所有错误,最终返回
errs MultiError
)
// 遍历所有的 Gatherer
for i, g := range gs {
// 调用当前 Gatherer 的 Gather 方法获取指标族
mfs, err := g.Gather()
if err != nil {
multiErr := MultiError{}
// 检查错误是否为 MultiError 类型
if errors.As(err, &multiErr) {
// 如果是 MultiError 类型,将其中的每个错误添加到最终错误集合中
for _, err := range multiErr {
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
}
} else {
// 如果不是 MultiError 类型,直接将错误添加到最终错误集合中
errs = append(errs, fmt.Errorf("[from Gatherer #%d] %w", i+1, err))
}
}
// 遍历当前 Gatherer 返回的所有指标族
for _, mf := range mfs {
// 检查当前指标族名称是否已经存在于 metricFamiliesByName 中
existingMF, exists := metricFamiliesByName[mf.GetName()]
if exists {
// 如果帮助信息不一致,添加错误信息并跳过当前指标族
if existingMF.GetHelp() != mf.GetHelp() {
errs = append(errs, fmt.Errorf(
"gathered metric family %s has help %q but should have %q",
mf.GetName(), mf.GetHelp(), existingMF.GetHelp(),
))
continue
}
// 如果指标类型不一致,添加错误信息并跳过当前指标族
if existingMF.GetType() != mf.GetType() {
errs = append(errs, fmt.Errorf(
"gathered metric family %s has type %s but should have %s",
mf.GetName(), mf.GetType(), existingMF.GetType(),
))
continue
}
} else {
// 如果不存在,创建一个新的 MetricFamily 实例
existingMF = &dto.MetricFamily{}
existingMF.Name = mf.Name
existingMF.Help = mf.Help
existingMF.Type = mf.Type
// 检查后缀冲突
if err := checkSuffixCollisions(existingMF, metricFamiliesByName); err != nil {
errs = append(errs, err)
continue
}
// 将新的 MetricFamily 实例添加到 metricFamiliesByName 中
metricFamiliesByName[mf.GetName()] = existingMF
}
// 遍历当前指标族中的所有指标
for _, m := range mf.Metric {
// 检查指标的一致性
if err := checkMetricConsistency(existingMF, m, metricHashes); err != nil {
errs = append(errs, err)
continue
}
// 将一致的指标添加到现有的 MetricFamily 实例中
existingMF.Metric = append(existingMF.Metric, m)
}
}
}
// 对合并后的指标族进行规范化处理,并返回最终结果和错误信息
return internal.NormalizeMetricFamilies(metricFamiliesByName), errs.MaybeUnwrap()
}
-
-
- Gatherers 本质上就是 []Gatherer,即多个 Gatherer 的集合。
- 它自己也实现了 Gatherer 接口,因此可以像单个 Gatherer 一样被使用。
-
核心能力:其Gather方法会依次调用所有子 Gatherer 的Gather方法,合并结果并处理冲突(如不同 Gatherer 产生相同指标时,保留最后一个值)。
典型场景:
- 整合来自不同注册中心的指标(如默认 Registry 与自定义 Registry)。
- 临时添加一次性指标采集逻辑(如按需触发的诊断指标)。
Gatherers 的设计使 Exporter 能够横向扩展指标来源,适应复杂监控场景的需求。
4.3、GathererFunc:函数式 Gatherer
https://github.com/prometheus/client_golang/blob/v1.22.0/prometheus/registry.go#L190
// GathererFunc turns a function into a Gatherer.
// GathererFunc 类型可以将一个函数转换为 Gatherer 接口的实现。
// 通过定义这个类型,我们可以让任何符合特定签名的函数表现得像一个 Gatherer,
// 从而方便地将自定义的指标收集逻辑集成到 Prometheus 的指标收集系统中。
type GathererFunc func() ([]*dto.MetricFamily, error)
// Gather implements Gatherer.
// Gather 方法实现了 Gatherer 接口。
// 当调用 GathererFunc 实例的 Gather 方法时,实际上是调用了该实例所代表的函数。
func (gf GathererFunc) Gather() ([]*dto.MetricFamily, error) {
// 调用 GathererFunc 类型的实例 gf 所代表的函数,并返回其结果。
// 这个函数应该返回一个包含 *dto.MetricFamily 指针的切片和一个错误对象。
return gf()
}
这段代码定义了一个名为 GathererFunc 的类型,它是一个函数类型,其签名为 func() ([]*dto.MetricFamily, error)。这个类型实现了 Gatherer 接口的 Gather 方法,使得任何符合该签名的函数都可以被当作 Gatherer 使用。这样的设计提供了一种灵活的方式,允许开发者通过定义自定义函数来实现指标的收集逻辑,并将其集成到 Prometheus 的指标收集流程中。
下面是一个使用 GathererFunc 的示例,展示了如何将自定义函数转换为 Gatherer 并集成到 Prometheus 的指标收集系统中。
package main
import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
dto "github.com/prometheus/client_model/go"
)
// 自定义的指标收集函数
func customMetricsGatherer() ([]*dto.MetricFamily, error) {
// 创建一个新的指标族
mf := &dto.MetricFamily{
Name: prometheus.StringToPtr("custom_metric"),
Help: prometheus.StringToPtr("This is a custom metric"),
Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{
{
Gauge: &dto.Gauge{
Value: prometheus.Float64ToPtr(42),
},
},
},
}
return []*dto.MetricFamily{mf}, nil
}
func main() {
// 将自定义函数转换为 Gatherer
customGatherer := prometheus.GathererFunc(customMetricsGatherer)
// 创建一个新的 HTTP 处理程序,用于暴露指标
http.Handle("/metrics", promhttp.HandlerFor(customGatherer, promhttp.HandlerOpts{}))
// 启动 HTTP 服务器
fmt.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("Error starting server:", err)
}
}
代码说明:
- 自定义指标收集函数:customMetricsGatherer 函数返回一个包含自定义指标的 dto.MetricFamily 切片。在这个例子中,我们创建了一个名为 custom_metric 的指标,类型为 GAUGE,值为 42。
- 将自定义函数转换为 Gatherer:使用 prometheus.GathererFunc 将 customMetricsGatherer 函数转换为 Gatherer 接口的实现。
- 创建 HTTP 处理程序:使用 promhttp.HandlerFor 创建一个 HTTP 处理程序,用于暴露指标。
- 启动 HTTP 服务器:使用 http.ListenAndServe 启动一个 HTTP 服务器,监听 :8080 端口。
五、架构设计的核心原则
从上述组件的设计可以看出,Prometheus Exporter 的架构遵循以下原则:
- 接口抽象:通过 Gatherer、Collector 等接口定义核心能力,使不同实现可以灵活替换。
- 职责单一:Collector 专注于采集,Gatherer 专注于汇总,HTTP 服务专注于暴露,各组件各司其职。
- 可扩展性:通过 Registry、Gatherers 等机制,支持横向扩展采集能力,适应不同规模的监控需求。
- 容错性:多 Gatherer 架构允许部分采集逻辑失败而不影响整体,提高系统稳定性。
这些原则为 Exporter 的定制化开发提供了清晰的指导,开发者可根据实际场景选择合适的架构模式(单 Gatherer 或多 Gatherer),并通过 Registry 和 GathererFunc 等工具灵活扩展功能。

浙公网安备 33010602011771号