CnOps 智能运维与可观测社区
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题
导航菜单
首页开源项目实践文章视频课程常见问答开发者工具STAROps 专题

CnOps 智能运维与可观测社区

愿景

CnOps 智能运维与可观测社区是一个以"智能运维与可观测"为核心的开放、包容、分享的技术社区,旨在聚集运维专家、开发者和爱好者,共同探讨、学习和分享可观测最佳实践与最新技术,与众多技术社区合作互动,共同探讨交叉领域的技术挑战,推动可观测领域的创新与进步。

内容社区

  • 实践文章
  • 视频课程
  • 开源项目
  • 常见问答
  • 开发者工具

友情链接

  • Prometheus
  • Grafana Lab
  • OpenTelemetry
  • LoongCollector

关注我们

阿里云云原生公众号阿里云云原生
阿里云可观测公众号阿里云可观测

Copyright © 2026 CnOps 社区. All rights reserved.

首页/开源项目/Prometheus/监控埋点
文档目录
指标与标签命名控制台和仪表盘监控埋点Histogram 和 summary告警记录规则何时使用 PushgatewayRemote write 调优Prometheus 之禅

Prometheus

监控埋点

本页提供了一组关于代码埋点的经验性指导建议。 简短的回答是:对所有东西进行埋点。每个库、子系统和服务都应该至少有一些指标,让你大致了解其运行状况。 埋点应该是代码的组成部分。在使用指标的同一文件中实例化指标类。这样在你追踪错误时,从告警到控制台再到代码会变得很容易。 出于监控目的,服务通常可以分为三种类型:在线服务、离线处理和批处理作业。它们之间有重叠,但每个服务往往能很好地归入其中一类。...

本页提供了一组关于代码埋点的经验性指导建议。

如何进行埋点

简短的回答是:对所有东西进行埋点。每个库、子系统和服务都应该至少有一些指标,让你大致了解其运行状况。

埋点应该是代码的组成部分。在使用指标的同一文件中实例化指标类。这样在你追踪错误时,从告警到控制台再到代码会变得很容易。

三种类型的服务

出于监控目的,服务通常可以分为三种类型:在线服务、离线处理和批处理作业。它们之间有重叠,但每个服务往往能很好地归入其中一类。

在线服务系统

在线服务系统是指人或另一个系统期望立即获得响应的系统。例如,大多数数据库和 HTTP 请求属于此类。

此类系统的关键指标是执行的查询数量、错误数和延迟。正在处理中的请求数量也可能有用。

关于统计失败查询,请参阅下方的失败章节。

在线服务系统应该同时在客户端和服务端进行监控。如果两侧观察到不同的行为,这对调试非常有用。如果一个服务有很多客户端,服务端不可能单独跟踪每个客户端,因此它们必须依赖自己的统计数据。

在统计查询时,要保持一致——是在开始时还是在结束时计数。建议在结束时计数,因为这与错误和延迟统计一致,而且编码通常也更容易。

离线处理

对于离线处理,没有人在主动等待响应,工作的批量处理很常见。也可能有多个处理阶段。

对于每个阶段,跟踪进入的项目数、正在处理的项目数、上次处理的时间以及发送出去的项目数。如果是批量处理,还应该跟踪进出的批次。

了解系统上次处理内容的时间对于检测系统是否停滞很有用,但这是非常局部化的信息。更好的方法是通过系统发送心跳:一些虚拟项目会完整通过整个流程,其中包含插入时的时间戳。每个阶段可以导出它所看到的最新心跳时间戳,让你了解项目通过系统需要多长时间。对于没有空闲期(始终有处理发生)的系统,可能不需要显式的心跳。

批处理作业

离线处理和批处理作业之间的界限是模糊的,因为离线处理可能以批处理作业的形式完成。批处理作业的特点是它们不会持续运行,这使得抓取变得困难。

批处理作业的关键指标是上次成功的时间。跟踪作业每个主要阶段花费的时间、总运行时间以及上次完成的时间(无论成功还是失败)也很有用。这些都是 gauge 类型的指标,应该推送到 PushGateway。通常还有一些总体的作业特定统计信息值得跟踪,例如处理的记录总数。

对于运行时间超过几分钟的批处理作业,使用基于拉取的监控进行抓取也很有用。这让你可以像其他类型的作业一样跟踪资源使用情况和与其他系统通信时的延迟等指标。如果作业开始变慢,这有助于调试。

对于运行非常频繁(比如每 15 分钟一次以上)的批处理作业,你应该考虑将其转换为守护进程,并作为离线处理作业来处理。

子系统

除了三种主要的服务类型外,系统还有需要监控的子组件。

库

库应该提供无需用户额外配置即可使用的埋点。

如果库用于访问进程外的某些资源(例如网络、磁盘或 IPC),至少应该跟踪总体查询数、错误数(如果可能出现错误)和延迟。

根据库的复杂程度,跟踪库内部的错误和延迟,以及你认为可能有用的任何一般性统计数据。

一个库可能被应用程序的多个独立部分用于不同的资源,因此在适当的地方使用标签来区分不同的用途。例如,数据库连接池应该区分它正在与哪些数据库通信,而 DNS 客户端库则不需要区分不同的用户。

日志

一般规则是,对于每行日志代码,你也应该有一个递增的计数器。如果你发现一条有趣的日志消息,你需要能够看到它发生的频率和持续时间。

如果在同一函数中有多条密切相关的日志消息(例如 if 或 switch 语句的不同分支),有时可以为所有这些消息递增同一个计数器。

通常,导出应用程序整体记录的 info/error/warning 日志行总数也很有用,并在发布流程中检查是否有显著差异。

失败

失败应该以类似于日志的方式处理。每次发生失败时,应该递增一个计数器。与日志不同的是,根据代码结构,错误可能还会冒泡到更通用的错误计数器。

在报告失败时,通常应该有另一个表示尝试总数的指标。这样可以轻松计算失败率。

线程池

对于任何类型的线程池,关键指标是排队的请求数、正在使用的线程数、线程总数、已处理的任务数以及处理耗时。跟踪任务在队列中等待了多长时间也很有用。

缓存

缓存的关键指标是总查询数、命中数、总体延迟,以及缓存前面的在线服务系统的查询数、错误数和延迟。

收集器

在实现非平凡的自定义指标收集器时,建议导出一个表示收集耗时(以秒为单位)的 gauge 和另一个表示遇到错误数的 gauge。

这是将持续时间导出为 gauge 而非 summary 或 histogram 的两种合理情况之一,另一种是批处理作业的持续时间。这是因为两者都代表特定推送/抓取的信息,而不是跟踪多个持续时间随时间的变化。

需要注意的事项

在进行监控时有一些需要注意的一般事项,特别是 Prometheus 特有的注意事项。

使用标签

很少有监控系统具有标签和表达式语言的概念来利用它们,因此需要一些时间来适应。

当你有多个想要相加/平均/求和的指标时,它们通常应该是带有标签的单个指标,而不是多个指标。

例如,与其使用 http_responses_500_total 和 http_responses_403_total,不如创建一个名为 http_responses_total 的单一指标,使用 code 标签表示 HTTP 响应码。然后你可以在规则和图表中将整个指标作为一个整体来处理。

作为经验法则,指标名称的任何部分都不应该是程序生成的(请改用标签)。唯一的例外是代理来自其他监控/埋点系统的指标。

另请参阅命名章节。

不要过度使用标签

每个标签集都是一个额外的时间序列,会产生 RAM、CPU、磁盘和网络开销。通常这些开销可以忽略不计,但在指标数量多且跨数百台服务器有数百个标签集的场景中,这些开销会迅速累积。

一般准则是,尽量将指标的基数保持在 10 以下,对于超过该数值的指标,力求在整个系统中将其限制在少数几个。绝大多数指标应该没有标签。

如果你有一个基数超过 100 或有可能增长到那么大的指标,请研究替代方案,例如减少维度数量或将分析从监控转移到通用处理系统。

为了让你更好地了解底层数字,让我们看看 node_exporter。node_exporter 为每个已挂载的文件系统导出指标。每个节点将有数十个时间序列用于诸如 node_filesystem_avail 的指标。如果你有 10,000 个节点,你最终会有大约 100,000 个 node_filesystem_avail 的时间序列,这对 Prometheus 来说是可以处理的。

如果你现在要添加每用户配额,拥有 10,000 个用户和 10,000 个节点将很快达到千万级别。这对当前的 Prometheus 实现来说太多了。即使数字较小,也存在机会成本,因为你无法在这台机器上再拥有其他可能更有用的指标了。

如果你不确定,从没有标签开始,随着具体用例的出现再添加更多标签。

Counter 与 gauge、summary 与 histogram

了解何时使用四种主要指标类型中的哪一种对于给定指标非常重要。

在 counter 和 gauge 之间选择有一个简单的经验法则:如果值可以下降,那它就是 gauge。

Counter 只能上升(和重置,例如进程重启时)。它们用于累计事件数量或每个事件的某些数量。例如,HTTP 请求的总数或 HTTP 请求中发送的字节总数。原始的 counter 值很少有用。使用 rate() 函数获取它们每秒的增长速率。

Gauge 可以设置、上升和下降。它们用于状态快照,例如正在处理的请求数、空闲/总内存或温度。你永远不应该对 gauge 使用 rate()。

Summary 和 histogram 是更复杂的指标类型,在专门章节中讨论。

使用时间戳,而非经过的时间

如果你想跟踪某件事发生以来经过了多长时间,请导出它发生时的 Unix 时间戳——而不是经过的时间。

导出时间戳后,你可以使用表达式 time() - my_timestamp_metric 来计算事件发生以来的时间,无需更新逻辑,并且可以防止更新逻辑卡住的情况。

内部循环

一般来说,埋点带来的额外资源开销远远被它为运维和开发带来的好处所抵消。

对于性能关键的代码或在给定进程中每秒调用超过 100,000 次的代码,你可能需要注意更新的指标数量。

一个 Java counter 的递增操作根据竞争程度需要 12-17ns。其他语言会有类似的性能表现。如果这个时间量对你的内部循环来说是显著的,请限制在内部循环中递增的指标数量,并尽可能避免使用标签(或缓存标签查找的结果,例如 Go 中 With() 的返回值或 Java 中 labels() 的返回值)。

同时注意涉及时间或持续时间的指标更新,因为获取时间可能涉及系统调用。与所有涉及性能关键代码的事项一样,基准测试是确定任何给定更改影响的最佳方式。

避免缺失指标

在某些事件发生之前不存在的时间序列很难处理,因为通常的简单操作不再足以正确处理它们。为避免这种情况,请为你事先知道可能存在的任何时间序列导出默认值(如 0)。

大多数 Prometheus 客户端库(包括 Go、Java 和 Python)会自动为没有标签的指标导出 0。

文章大纲