请参与 Prometheus 用户调研(2026 年 3 月版) ,帮助社区确定未来开发工作的优先级!

编写导出器

如果你正在对自己的代码进行检测(instrumenting),则应遵循如何使用 Prometheus 客户端库检测代码的一般规则。但在从其他监控或检测系统获取指标时,情况往往没有那么非黑即白。

本文档包含你在编写导出器(exporter)或自定义收集器时应该考虑的问题。所涉及的理论对于直接进行检测的开发者同样具有参考价值。

如果你在编写导出器时对本文档的任何内容有疑问,请通过 IRC(libera 上的 #prometheus 频道)或邮件列表与我们联系。

可维护性与纯净度

编写导出器时你需要做出的主要决定是:为了获得完美的指标,你愿意投入多少工作量。

如果相关系统只有少数几个很少变化的指标,那么把一切做到极致是一个轻松的选择,HAProxy 导出器 就是一个很好的例子。

另一方面,如果系统有数百个指标且随版本频繁变更,而你试图追求完美,那么你将面临大量持续性的维护工作。MySQL 导出器 就处于这个谱系的末端。

Node 导出器 是两者的结合,复杂度随模块而异。例如,mdadm 收集器手动解析文件并暴露专门为该收集器创建的指标,因此我们不妨将其做得准确。对于 meminfo 收集器,结果因内核版本而异,所以我们只做必要的转换以创建有效的指标。

配置

当处理应用程序时,你的目标应该是提供一个无需用户进行额外自定义配置的导出器,用户只需告知导出器应用程序的位置即可。你可能还需要提供过滤特定指标的能力,以防某些指标在大型环境中过于精细且开销昂贵,例如 HAProxy 导出器  允许过滤每个服务器的统计信息。同样,可能存在默认禁用的昂贵指标。

当处理其他监控系统、框架和协议时,你通常需要提供额外的配置或自定义,以生成适合 Prometheus 的指标。在最理想的情况下,监控系统具有与 Prometheus 足够相似的数据模型,你可以自动确定如何转换指标。例如 Cloudwatch SNMP collectd  就是这种情况。最多,我们需要让用户选择他们想要获取哪些指标。

在其他情况下,来自系统的指标完全是非标准的,具体取决于系统的使用方式和底层应用程序。这种情况下,用户必须告诉我们如何转换这些指标。JMX 导出器  是这方面的典型案例,而 Graphite StatsD  导出器也需要配置来提取标签。

建议确保导出器无需配置即可开箱即用,并在需要时提供一系列示例配置来进行转换。

YAML 是标准的 Prometheus 配置格式,所有配置默认应使用 YAML。

指标

命名

遵循关于指标命名的最佳实践

通常,指标名称应让熟悉 Prometheus 但不熟悉特定系统的人也能很好地猜测出指标的含义。名为 http_requests_total 的指标并不是特别有用——它是指请求进入时、在某个过滤器中,还是到达用户代码时测量的?而 requests_total 更糟,什么类型的请求?

在直接检测中,给定的指标应仅存在于一个文件中。相应地,在导出器和收集器中,一个指标应仅适用于一个子系统,并据此命名。

除了编写自定义收集器或导出器外,指标名称永远不应由程序生成。

应用程序的指标名称通常应以导出器名称为前缀,例如 haproxy_up

指标必须使用基本单位(例如秒、字节),并将转换为更易读的单位留给绘图工具。无论你最终使用什么单位,指标名称中的单位必须与实际使用的单位相匹配。同样,暴露比率,而不是百分比。更好的是,为比率的两个组成部分分别指定计数器。

指标名称不应包含它们导出的标签,例如 by_type,因为如果标签被聚合掉,这种命名将不再合理。

唯一的例外是当你通过多个指标导出带有不同标签的相同数据时,这是区分它们最理性的方式。对于直接检测,这仅在“用所有标签导出单个指标会导致过高的基数”时才会出现。

Prometheus 的指标和标签名称使用 snake_case(蛇形命名法)。将 camelCase 转换为 snake_case 是值得提倡的,尽管自动转换并不总是能产生很好的结果,例如 myTCPExampleisNaN,因此有时最好保持原样。

暴露的指标不应包含冒号,冒号保留用于用户在聚合时自定义记录规则。

只有 [a-zA-Z0-9:_] 在指标名称中是有效的。

_sum_count_bucket_total 后缀由 Summaries、Histograms 和 Counters 使用。除非你正在生成这些类型之一,否则请避免使用这些后缀。

_total 是计数器的约定,如果你使用 COUNTER 类型,则应使用它。

process_scrape_ 前缀是保留的。如果它们遵循匹配的语义,你可以在这些前缀上添加自己的前缀。例如,Prometheus 拥有用于衡量采集耗时的 scrape_duration_seconds,实践中最好也拥有一个以导出器为中心的指标,例如 jmx_scrape_duration_seconds,用以表示该特定导出器执行任务所花费的时间。对于拥有 PID 访问权限的进程统计信息,Go 和 Python 都提供了为你处理此问题的收集器。HAProxy 导出器 就是一个很好的例子。

当你同时拥有成功请求计数和失败请求计数时,最好的展现方式是将它们作为两个独立的指标——一个表示总请求,另一个表示失败请求。这使得计算失败比率变得简单。不要使用带 failed 或 success 标签的单个指标。同样,对于缓存命中或未命中,最好将总数作为一个指标,命中数作为另一个指标。

考虑监控用户对指标名称进行代码搜索或 Web 搜索的可能性。如果名称是非常确定的,并且不太可能在习惯这些名称的人群范围之外使用(例如 SNMP 和网络工程师),那么保持原样可能是一个好主意。这种逻辑并不适用于所有导出器,例如 MySQL 导出器的指标可能被各类人群使用,而不仅仅是 DBA。带有原始名称的 HELP 字符串可以提供与使用原始名称相同的大部分好处。

标签

阅读关于标签的一般建议

避免使用 type 作为标签名称,它太通用且通常毫无意义。你也应该尽可能避免使用容易与目标标签冲突的名称,例如 regionzoneclusteravailability_zoneazdatacenterdcownercustomerstageserviceenvironmentenv。但是,如果这就是应用程序对某种资源的称呼,最好不要通过重命名来造成混淆。

避免仅因为它们共享前缀就将事物放入一个指标的诱惑。除非你确定将它们合为一个指标是有意义的,否则多个指标会更安全。

标签 le 对直方图有特殊意义,quantile 对摘要有特殊意义。一般应避免使用这些标签。

读/写和发送/接收最好作为单独的指标,而不是作为标签。这通常是因为你一次只关心其中一个,而且这样使用起来更方便。

经验法则是,一个指标在进行求和或求平均时应该是有意义的。导出器中会出现另一种情况,即数据本质上是表格形式的,否则将要求用户使用正则表达式处理指标名称才能使用。以主板上的电压传感器为例,虽然对它们进行数学运算毫无意义,但将它们放在一个指标中比每个传感器一个指标更有意义。指标中的所有值几乎总是应该具有相同的单位,例如,如果风扇速度与电压混合在一起,并且你无法自动将它们分开,那就需要慎重考虑了。

不要这样做

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{label="total"} 7

或这样做

my_metric{label="a"} 1
my_metric{label="b"} 6
my_metric{} 7

前者会破坏对你的指标进行 sum() 的用户的操作,而后者会破坏求和并且非常难以使用。一些客户端库(例如 Go)会主动阻止你在自定义收集器中执行后者,所有客户端库都应阻止你通过直接检测来执行后者。永远不要做这两者中的任何一个,而是依靠 Prometheus 的聚合功能。

如果你的监控工具暴露了类似的总数,请丢弃该总数。如果由于某种原因必须保留它(例如总数包含未单独计算的事物),请使用不同的指标名称。

检测标签应保持最简,用户在编写 PromQL 时需要考虑每一个额外的标签。因此,应避免使用那些可以在不影响时间序列唯一性的情况下移除的检测标签。关于指标的额外信息可以通过信息指标(info metric)添加,关于如何处理版本号的示例,请参阅下文。

但是,在某些情况下,几乎所有指标用户都期望获得额外信息。如果是这样,添加一个非唯一标签(而不是信息指标)是正确的解决方案。例如 mysqld_exporter mysqld_perf_schema_events_statements_total 指标中的 digest 标签,它是完整查询模式的哈希值,足以保证唯一性。然而,如果没有人类可读的 digest_text 标签,它几乎没用;而对于长查询,该标签仅包含查询模式的开头,因此不是唯一的。所以我们最终同时使用了面向人类的 digest_text 标签和保证唯一性的 digest 标签。

目标标签,而非静态采集标签

如果你发现自己想对所有指标应用相同的标签,请停下来。

通常有两种情况会出现这种情况。

第一种情况是某些标签如果放在指标上会很有用,例如软件的版本号。此时,应改用 https://www.robustperception.io/how-to-have-labels-for-machine-roles/  中描述的方法。

第二种情况是当标签实际上是目标标签(target label)时。这些是区域、集群名称等信息,它们来自你的基础设施配置,而不是应用程序本身。应用程序不应该决定它在你的标签分类法中处于什么位置,那是运行 Prometheus 服务器的人需要配置的,而监控同一应用程序的不同人可能会给它不同的名称。

因此,这些标签应通过你使用的任何服务发现机制,在 Prometheus 的采集配置(scrape configs)中设置。在此处应用机器角色的概念也是可以的,因为它对至少部分采集该指标的人来说可能是非常有用的信息。

类型

你应该尽量将你的指标类型与 Prometheus 类型匹配。这通常意味着计数器(Counters)和仪表(Gauges)。摘要(Summaries)的 _count_sum 也相对常见,有时你还会看到分位数(Quantiles)。直方图(Histograms)比较罕见,如果你遇到了一个,记住暴露格式暴露的是累积值。

通常,指标类型并不显而易见,尤其是在自动处理一组指标时。通常 UNTYPED 是一个安全的选择。

计数器不能减少,因此如果你从另一个可能递减的检测系统(例如 Dropwizard metrics)获取计数器类型,那么它不是计数器,而是仪表。此时 UNTYPED 可能是最好的选择,因为如果它被用作计数器,GAUGE 可能会产生误导。

帮助字符串 (Help strings)

当你转换指标时,用户能够追踪回原始指标以及导致该转换的规则是非常有用的。在帮助字符串中放入收集器或导出器的名称、所应用的任何规则 ID,以及原始指标的名称和详细信息,将极大地帮助用户。

Prometheus 不喜欢同一个指标拥有不同的帮助字符串。如果你正从多个指标合成一个指标,请选择其中一个作为帮助字符串的内容。

关于此的示例,SNMP 导出器使用了 OID,JMX 导出器放入了一个示例 mBean 名称。HAProxy 导出器  拥有手写的字符串。Node 导出器  也有各种各样的示例。

丢弃低价值统计信息

一些检测系统除了提供最小值、最大值和标准差之外,还会公开 1 分钟、5 分钟、15 分钟的速率以及自应用程序启动以来的平均速率(例如,在 Dropwizard 指标中这些被称为 mean)。

这些都应该被舍弃,因为它们用处不大且会增加冗余。Prometheus 可以自行计算速率,而且通常更准确,因为公开的平均值通常是指数衰减的。你无法得知最小值或最大值的计算时长,而标准差在统计上也是无用的;如果需要计算,你随时可以公开平方和、_sum_count

分位数(Quantiles)也有类似的问题,你可以选择丢弃它们,或者将它们放入一个汇总(Summary)中。

点分字符串

许多监控系统没有标签(labels),而是采用诸如 my.class.path.mymetric.labelvalue1.labelvalue2.labelvalue3 这样的命名方式。

Graphite StatsD  导出器共享一种通过小型配置语言来转换这些字符串的方法。其他导出器也应实现相同的功能。这种转换目前仅用 Go 语言实现,如果能将其抽象为一个独立的库将会很有益处。

收集器(Collectors)

在为导出器实现收集器时,不应使用通常的直接检测方法,然后在每次抓取时更新指标。

相反,应该每次都创建新的指标。在 Go 中,这是通过在 Collect() 方法中使用 MustNewConstMetric  来实现的。对于 Python,请参阅 https://github.com/prometheus/client_python#custom-collectors ;对于 Java,请在你的 collect 方法中生成 List<MetricFamilySamples>,参见 StandardExports.java  获取示例。

这样做有两个原因。首先,两次抓取可能同时发生,而直接检测使用了实际上是文件级的全局变量,因此会出现竞态条件。其次,如果某个标签值消失了,它仍会被导出。

通过直接检测来监控导出器本身是可以的,例如导出器在所有抓取过程中传输的总字节数或执行的调用次数。对于诸如 blackbox exporter SNMP exporter  这样不绑定到单一目标的导出器,这些指标只应在标准的 /metrics 调用时公开,而不是在对特定目标进行抓取时公开。

关于抓取本身的指标

有时你可能希望导出关于抓取本身的指标,比如抓取耗时或处理的记录数。

这些应该作为仪表(gauges)公开,因为它们关联的是一个事件(抓取),并且指标名称应以导出器名称为前缀,例如 jmx_scrape_duration_seconds。通常 _exporter 后缀会被省略,如果该导出器也可以单纯作为收集器使用,则绝对应该省略它。

其他抓取“元”指标应尽量避免。例如,抓取次数的计数器或抓取持续时间的直方图。让导出器跟踪这些指标会重复 Prometheus 本身自动生成的指标。这会增加每个导出器实例的存储成本。

机器和进程指标

许多系统(例如 Elasticsearch)会公开 CPU、内存和文件系统信息等机器指标。由于 node exporter  已经在 Prometheus 生态系统中提供了这些指标,因此类似的指标应该被丢弃。

在 Java 领域,许多检测框架会公开进程级和 JVM 级的统计信息,如 CPU 和 GC。Java 客户端和 JMX 导出器已经通过 DefaultExports.java  以首选形式包含了这些内容,因此这些指标也应该被丢弃。

其他语言和框架也是如此。

部署

每个导出器应该只监控一个应用程序实例,最好是和它一起运行在同一台机器上。这意味着你运行的每一个 HAProxy,都应该对应一个 haproxy_exporter 进程。每一台装有 Mesos worker 的机器上都应运行 Mesos exporter ;如果某台机器同时拥有 master,则还要为 master 再运行一个。

这背后的理论是,对于直接检测,你本来就是这么做的,我们在其他布局中也尽量向此靠拢。这意味着所有服务发现都在 Prometheus 中完成,而不是在导出器中。这样做还有一个好处,即 Prometheus 拥有了用户通过 blackbox exporter  探测服务所需的全部目标信息。

有两个例外情况:

第一种是运行在所监控应用程序旁边完全不合理的情况。SNMP、blackbox 和 IPMI 导出器是其中的主要示例。IPMI 和 SNMP 导出器之所以如此,是因为这些设备通常是无法运行代码的“黑盒”(尽管如果你能改为在它们上面运行 node exporter 会更好);blackbox 导出器的情况是,你监控的是像 DNS 名称这样的东西,那里也没有东西可以运行。在这种情况下,Prometheus 仍然应该执行服务发现,并将目标传递给导出器去抓取。有关示例,请参阅 blackbox 和 SNMP 导出器。

注意,目前只有使用 Go、Python 和 Java 客户端库才能编写这种类型的导出器。

第二种例外情况是,你只是从某个系统的随机实例中提取统计数据,而不关心具体连接的是哪一个。考虑一组 MySQL 从库,你想针对数据运行一些业务查询来进行导出。使用你通常的负载均衡方法连接其中一个从库,是最合理的方案。

当你监控具有主从选举机制的系统时,这并不适用。在这种情况下,你应该单独监控每个实例,并在 Prometheus 中处理“主节点”逻辑。这是因为主节点并不总是只有一个,而且在 Prometheus 不知情的情况下更改目标会导致各种奇怪的问题。

调度

只有当 Prometheus 抓取指标时,才应从应用程序中拉取数据,导出器不应基于自己的计时器执行抓取。也就是说,所有抓取都应该是同步的。

因此,你不应在公开的指标上设置时间戳,让 Prometheus 来处理。如果你认为自己需要时间戳,那么你可能需要的是 Pushgateway

如果某个指标获取成本非常高(即耗时超过一分钟),那么进行缓存是可以接受的。这应该在 HELP 字符串中注明。

Prometheus 的默认抓取超时时间是 10 秒。如果你的导出器预计会超过此时间,你应该在用户文档中明确说明。

推送(Pushes)

有些应用程序和监控系统只推送指标,例如 StatsD、Graphite 和 collectd。

这里有两点需要考虑。

首先,何时使指标过期?Collectd 和与 Graphite 通信的系统都会定期导出数据,当它们停止时,我们需要停止公开这些指标。Collectd 包含过期时间,所以我们使用它;Graphite 没有,因此它在导出器中表现为一个标志。

StatsD 有点不同,因为它处理的是事件而不是指标。最好的模式是每个应用程序旁边运行一个导出器,并在应用程序重启时重启它们,以便清除状态。

其次,这类系统通常允许用户发送增量(deltas)或原始计数器。你应该尽可能依赖原始计数器,因为这是 Prometheus 的通用模型。

对于服务级指标(例如服务级批处理作业),你应该让导出器推送到 Pushgateway 并在事件完成后退出,而不是自己处理状态。对于实例级批处理指标,目前还没有明确的模式。可选方案包括滥用 node exporter 的 textfile 收集器、依赖内存状态(如果你不需要跨重启保持状态,这可能是最好的),或实现类似 textfile 收集器的功能。

失败的抓取

目前对于无法响应或出现其他问题的应用程序,有两种抓取失败的处理模式。

第一种是返回 5xx 错误。

第二种是设置一个 myexporter_up 变量(例如 haproxy_up),其值为 0 或 1,具体取决于抓取是否成功。

如果即便抓取失败仍能获取一些有用的指标(例如 HAProxy 导出器提供进程统计信息),那么后者更好。前者对于用户来说处理起来稍微容易一些,因为 up 以通常的方式工作,尽管你无法区分是导出器宕机还是应用程序宕机。

落地页

如果用户访问 http://yourexporter/ 能看到一个包含导出器名称以及指向 /metrics 页面链接的简单 HTML 页面,这对用户会更友好。

端口号

用户可能在同一台机器上运行许多导出器和 Prometheus 组件,因此为了简化这一过程,每个组件都有一个唯一的端口号。

https://github.com/prometheus/prometheus/wiki/Default-port-allocations  是我们追踪这些端口的地方,它是公开可编辑的。

在开发你的导出器时,请随意使用下一个空闲端口号,最好在公开宣布之前进行分配。如果你还没有准备好发布,写上你的用户名和 WIP(开发中)也是可以的。

这是一个旨在让用户生活更轻松的注册表,而不是开发特定导出器的承诺。对于内部应用程序的导出器,我们建议使用默认端口分配范围之外的端口。

发布公告

一旦你准备好向全世界发布你的导出器,请通过邮件列表发送公告,并通过编辑 此 GitHub 仓库文件  提交 PR,将其添加到可用导出器列表中。

本页内容