这是因为 Prometheus 优先考虑范围的一致定义而不是准确性。即它总是将范围定义为落在(包含)区间[now() - range, now()] 内的所有样本。这个定义对仪表非常有意义:如果你想计算avg_over_time()
一个时间范围等于步长的,你希望每个输入样本都包含在恰好一个输出样本的计算中。
但对于柜台来说,情况并非如此。在时间范围等于步长的情况下,一个输入值(即两个连续样本之间的增量)基本上被丢弃。(有关更多详细信息,请参阅 Prometheus 问题#3746和3806。)为了弥补它丢弃的数据,Prometheus 使用外推法来调整计算结果。
这意味着如果(如您的情况)您使用的时间范围是您的刮擦间隔的 2 倍(刮擦间隔的1m
范围30s
),Prometheus 将(平均)在每个范围内找到 2 个样本,但这 2 个样本所涵盖的实际时间范围将待在身边30s
。因此,Prometheus 将1m
通过将值加倍来帮助将速率推断为请求的值。因此结果是 2 而不是预期的 1。您还会注意到,由于连续样本之间的一些增加被丢弃(即使没有样本),并不是您的计数器中的所有增加都显示在您的rate()
图表中。(即没有跳转rate()
对应于第三次计数器增加。如果你在不同的时间刷新,不同的增加会出现和消失。Grafana 通过始终将请求的范围与步骤对齐来“解决”后者,因此始终缺少相同的增加。)
Prometheus 开发人员建议的解决方案是计算更长持续时间的费率。但所做的只是减少误差(你会得到 1.5 与 3x 范围,1.33 与 4x 范围,1.25 为 5x 范围等),永远不会摆脱它。普罗米修斯的推断被平稳地增加计数器隐藏得足够好,但像你自己的计数器一样突出,很少增加。)
这个问题的唯一解决方法(没有修复 Prometheus,我已经为此提交了 PR 并正在维护一个 fork)是对 Prometheus 的rate()
. 即假设一个30s
表达式的抓取间隔rate(foo[1m])
应该替换为:
rate(foo[90s]) * 60 / 90
或更一般地(注意括号内的表达式需要是时间文字,不能是计算)
rate(foo[intended_range + scrape_interval]) * intended_range / (intended_range + scrape_interval)
这样做的原因是该intended_range + scrape_interval
范围将为您提供足够的样本来覆盖 的增加intended_range
,这就是您想要的。但是随后您必须撤消 Prometheus 的推断引入的变化,因此随之而来的乘法和除法。这是一个丑陋的黑客,取决于你知道你的抓取间隔并将其硬编码到你的记录规则和/或 Grafana 查询中。
请注意,无论您使用哪种方法,您都可能不会得到正好为 1 的值。由于服务、网络和内部 Prometheus 延迟,样本通常不会在毫秒内对齐,因此每秒的增长率会略低于或略高于预期值。