为何Prometheus rate函数在序列正常时返回异常高值?
问题描述
我有一个计数器序列,直接查询时显示正常:
energy_watthours_total{meter="export"}
响应数据(核心片段):
{"status":"success", "data": {"resultType":"matrix", "result": [{"metric": {"__name__":"energy_watthours_total","instance":"XXX","job":"XXX","meter":"export"}, "values": [ [1728564000,"24335660"], [1728564014,"24335660"], [1728564028,"24335736"], [1728564042,"24335730"], // 此处数值较前一个点下降 [1728564056,"24335798"], ... ] }] } }
但查询其速率时,偶尔会得到异常高的值:
rate(energy_watthours_total{meter="export"}[1m])
响应中出现异常值(核心片段):
{"status":"success", "data": {"resultType":"matrix", "result": [{"metric": {"instance":"XXX","job":"XXX","meter":"export"}, "values":[ [1728564000,"3.822222222222222"], [1728564014,"3.822222222222222"], [1728564028,"4.266666666666666"], [1728564042,"540796.8666666666"], // 异常高值 [1728564056,"540797.2"], // 后续点受连锁影响 ... ] }] } }
TSDB dump确认存在计数器数值下降的情况:
$ promtool tsdb dump --min-time=1728564000000 --max-time=1728564196000 --match='energy_watthours_total{meter="export"}' {__name__="energy_watthours_total", instance="XXX", job="XXX", meter="export"} 2.4335736e+07 1728564014395 {__name__="energy_watthours_total", instance="XXX", job="XXX", meter="export"} 2.433573e+07 1728564029395 // 数值从24335736降到24335730 ...
该问题影响rate、irate和increase,但不影响delta;推测与计数器重置检测有关,想知道具体原因及解决方法?
原因分析
这确实是Prometheus计数器重置检测逻辑导致的问题:
- 计数器非预期下降:你的计数器在某个时间点出现了数值减少,这违反了计数器单调递增的规则(正常情况下计数器只会上升,仅在设备重启等场景下才会归零重置)。
- Prometheus重置处理逻辑:
rate/irate/increase函数默认会检测计数器重置:当发现后一个样本值小于前一个样本值时,会判定计数器已重置为0并重新计数。此时计算差值时,会自动加上**无符号64位整数的最大值(2^64-1)**到当前样本值,再减去前一个样本值,导致差值异常巨大,最终计算出的速率飙升。 delta不受影响的原因:delta函数仅计算样本的原始差值,不会处理计数器重置,所以即使数值下降,也只会得到负数差值,不会出现异常高值。
解决方法
1. 根源修复:解决计数器数值下降问题
这是最彻底的方案,需要排查数据源:
- 检查设备固件是否存在bug,导致计数器偶尔回滚;
- 排查数据采集逻辑,确认是否存在重复上报、数值计算错误的情况;
- 确保计数器严格遵循单调递增规则,仅在设备重启等真正重置场景下归零。
2. 规避重置检测:修改PromQL查询
如果暂时无法修复数据源,可以通过修改查询逻辑跳过假重置的影响:
方案A:用delta手动计算速率并过滤负数
max(delta(energy_watthours_total{meter="export"}[1m]), 0) / 60
- 原理:
delta计算原始差值,max(..., 0)将负数差值(假重置或真重置)转为0,再除以时间范围(60秒)得到速率; - 优点:避免假重置导致的异常高值,同时处理真重置的情况;
- 缺点:无法利用
rate/irate的平滑逻辑,适合短时间范围查询。
方案B:排除触发重置检测的样本
rate(energy_watthours_total{meter="export"}[1m]) unless (energy_watthours_total{meter="export"} - last_over_time(energy_watthours_total{meter="export"}[1m])) < 0
- 原理:判断当前样本是否比1分钟内的最后一个样本小,若是则排除该点的速率值;
- 优点:保留
rate的平滑特性,仅过滤异常点; - 缺点:会丢失触发条件的样本数据,若存在真重置场景需要额外处理。
方案C:使用resets函数标记重置
rate(energy_watthours_total{meter="export"}[1m]) unless resets(energy_watthours_total{meter="export"}[1m]) > 0
- 原理:
resets函数统计指定时间范围内的重置次数,若次数大于0则排除该点的速率值; - 优点:精准过滤触发重置检测的点;
- 缺点:同样会丢失重置点的数据,需结合业务场景判断是否接受。
内容的提问来源于stack exchange,提问作者b0fh
相关产品推荐
相关产品推荐

