Prometheus 的几个常见问题和错误用法

总结 Prometheus 使用中的常见问题和错误用法,包括 step 设置不合理、Grafana rate 时间范围写死、高基数标签、重复上报数据、without 和 by 聚合选择等。

作者 巴辉特

夜莺有十几个交流群,经常会在群里看到一些奇奇怪怪的问题,有些问题是由于对 Prometheus 的理解不够深入导致的,有些问题是由于错误的用法导致的。本文总结了一些常见的问题和错误用法,希望能帮助大家更好地使用 Prometheus。

核心要点摘要

  • Prometheus 图表看不到历史峰值,常见原因之一是查询 step 设置不合理。
  • Grafana 里 rate() 时间范围不要写死,长时间范围查询更适合使用 $__rate_interval
  • 标签值不可枚举会导致高基数问题,用户 ID、订单 ID、手机号、IP、TraceID 等通常不适合作为 label value。
  • Counter 指标出现巨大 rate 值时,要排查是否有重复采集器或相同 hostname 重复上报。
  • PromQL 聚合时,bywithout 会影响保留哪些标签,写错会导致结果信息不足或不够通用。

常见错误速览

问题 典型表现 建议
step 设置不合理 昨天的峰值今天看不到 调整查询 step,避免采样点错过峰值
rate() 范围写死 长时间范围图表出现断点 在 Grafana 中使用 $__rate_interval
高基数标签 指标数量持续膨胀 用可枚举值做标签,动态 ID 放到其他存储分析
重复上报 Counter 的 rate 结果巨大 检查采集器数量、hostname 和上报间隔
by/without 使用不当 聚合后标签丢失或表达式不通用 明确要保留或排除的标签

Prometheus 中昨天的峰值今天查看就看不到了

这个问题是因为 Prometheus 查询时 step 参数设置不合理导致的,具体可以参考前两天的文章《Prometheus 昨天的峰值今天就看不到了,这监控不准啊》,不再赘述。

Grafana 中的图表,rate 函数写死了时间范围

这个问题是因为 rate 函数的时间范围写死了,导致图表的时间范围变化时,rate 函数的时间范围不变,从而导致图表的数据出现断点,比如下面的 promql:

rate(node_cpu_seconds_total{mode="idle"}[5m]) * 100

这个 promql 使用的时间范围是 5m,如果查看最近 1h 的数据或者 2h 的数据看起来都是正常的,但是如果要查看 7d 或 30d 的数据,Grafana 自动计算的 step 远大于 5m,导致 rate 函数的时间范围内经常没有数据,导致图表出现断点。

最佳实践是使用 $__rate_interval 变量,这个变量会根据用户所选时间范围自动计算 rate 函数的时间范围,通常来讲是 4 倍的 step 值,确保 rate 函数的时间范围内总能找到数据点。

如果是使用夜莺的仪表盘,也是一样的道理,为了降低用户迁移成本,夜莺的仪表盘也会使用 $__rate_interval 变量,和 Grafana 保持一致。

标签值不可枚举,导致高基数问题

在 Prometheus 生态中,每个指标的唯一标识就是一堆 labels(指标名也是一个特殊的 label,其 label key 是 __name__),如果某个指标,其 labels 不同,就会被当做两个指标来处理,比如下面的指标:

http_requests_total{method="GET", handler="/user/1"} 100
http_requests_total{method="GET", handler="/user/2"} 200

method 表示 HTTP 请求的方法,这个是可以枚举的,一般就是 GET、POST、PUT、DELETE 等,但是上例中的 handler 是一个 URL 路径,其中的数字表示 userid,这个是不可枚举的,因为 user 的量会越来越大,导致 handler 标签的 label value 越来越多,导致指标数量越来越多,最终影响 Prometheus 性能,这就是高基数问题。

对于现成的各类 Exporter(尤其是 star 数量较高的),通常不会有这个问题,因为写 Exporter 代码的人,通常是了解高基数的危害的。高基数问题通常出自那些自行使用 Prometheus SDK 埋点的新手。

仅就上例 handler 这个 label 而言,最佳实践是把 urlpath 中的 userid 替换成变量表示,而非直接使用具体的数字,比如:

http_requests_total{method="GET", handler="/user/:userid"} 300

通常来讲,userid、orderid、phone、ip、email、traceid、requestid 等信息如果放到 label value 里,大概率会引发高基数问题。这些高基数需求,可以尝试换个存储,比如使用 ClickHouse 这类 OLAP 数据库来做统计分析。

判断标签是否合适,一个简单办法是问:这个 label value 是否可以枚举,数量是否可控。如果答案是否定的,就不要轻易放进 Prometheus 标签里。

重复上报数据导致问题

大家应该都知道 rate 函数用于 Counter 类型的数据。有时我们可能会看到某个 rate 的结果巨大无比,比如网络流量、IO Bytes 等出现一些巨大无比的值。

这个问题通常是由于数据重复上报导致的,比如我在夜莺的即时查询中使用 Table 视图查看某个 Counter 的 range query 结果如下:

这里可以看到数据上报的时间差稳定在 15s,说明只有一个采集器在上报数据,是正常的。此时我又重复启动了一个采集器,再次查询,结果如下:

从时间戳的差值上来看,间隔已经不是原本预期的 15s 了,而是一会是一个值,一会又是另一个值,交叉出现,很规律。这就是典型的数据重复上报。

对于我这个测试场景而言,重复上报数据问题不大,因为我是在一个机器上启动了两个采集器,但是,如果是在不同机器上启动了两个采集器(其 hostname 相同)就麻烦了,一个机器可能启动了较长时间,其 Counter 值已经很大了,另一个机器启动时间较短,其 Counter 值较小,这样就会导致 rate 函数的结果巨大无比,大家想想是为啥?

注意 without 和 by

如果你使用 Node-Exporter 采集机器监控数据,那你一定知道 node_cpu_seconds_total 这个指标,这个指标表示 CPU 时间,单位是秒,Counter 类型,我查一个例子给你看看:

这个指标有 4 个 label:

  • cpu:CPU 编号,从 0 开始
  • instance:Exporter 的地址
  • job:Job 名称
  • mode:CPU 时间类型,比如 idle、system、user 等,我这里只查询了 idle

OK,现在我想计算整机 CPU 利用率,应该怎么做?典型的做法如下:

avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) * 100

但是这样一来,结果中只有 instance 标签了,我还想要 job 标签怎么办?OK,那就加上 job 标签:

avg(1 - rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance, job) * 100

但是有些公司会打上更多自定义标签,比如 env、region 等,上面的 promql 会把 env、region 等抹掉,不通用,这时候就可以使用 without 关键字:

avg without (mode,cpu) ( 1 - rate(node_cpu_seconds_total{mode="idle"}[5m]) ) * 100

这样一来,只有 mode 和 cpu 两个 label 被抹掉,其他 label 都保留了。

总结

还有哪些坑?欢迎大家留言补充哈。

Prometheus 很强,但它对使用方式很敏感。step、rate 区间、标签基数、重复上报、聚合标签这些细节,都会直接影响图表、告警和排障结论。很多问题看起来像“监控不准”,本质上是查询参数、采集配置或指标设计不合理。

想把 Prometheus 用稳,核心不是记住更多函数,而是把几个基本原则坚持住:查询窗口跟随展示范围,标签值可枚举,采集源唯一,Counter 正确使用 rate(),聚合时保留必要上下文。

FAQ

Q1:Grafana 里为什么不建议把 rate() 写死成 [5m] A:当图表时间范围变成 7d 或 30d 时,Grafana 自动计算的 step 可能远大于 5m,导致 rate() 范围内经常没有数据点,图表就容易断点。

Q2:哪些值不适合放进 Prometheus label? A:通常 userid、orderid、phone、ip、email、traceid、requestid 等不可枚举或数量持续增长的值,不适合放到 label value 里。

Q3:为什么重复上报会让 Counter 的 rate 结果异常大? A:如果不同采集器用相同 hostname 上报同一类 Counter,两个序列可能交叉出现。一个 Counter 值大、另一个值小,会让 rate() 计算出异常结果。

Q4:withoutby 怎么选? A:by 是显式保留指定标签,without 是排除指定标签并保留其他标签。如果环境里有 env、region 等自定义标签,without 往往比只写 by(instance, job) 更通用。

延伸路径

继续看解决方案和产品对比

如果你正在做监控、可观测性或故障定位相关选型,建议从解决方案和产品对比继续往下看。

标签 Prometheus
快猫星云 联系方式 快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云 联系方式
快猫星云