首先,让我们从一个 bug 说起,上周五, 有用户反馈,说我们的 API 每日请求统计为负数。

先简单解释下我们的每日统计是怎么实现的:

  1. 我们有个日志分析程序持续分析最新的日志(5分钟粒度),它会将分析结果推送到 pushgateway, 然后 prometheus 定时从 pushgateway 拉取数据。
  2. 我们定义了一个 metric 来表示收集到的数据,它是一个 counter 类型(不断递增)。
  3. 我们使用 PromQL 的 delta 语法, 选择从 00:00 ~ 23:59 的区间作为每日访问量。

这个计算逻辑初看上去很合理,但是它存在一个前提,那就是确保 Prometheus 存储的 metric 数值一定是递增。

那用户提的这个 bug 是不是就是这个原因导致呢?

我查看了过去几日的数据,它几乎就是一直递增的,只是中间出现了一次置零,看来我们已经找到点蛛丝马迹,那为啥 counter 会出现置零情况?

要搞懂这个问题,必须先弄清楚这条 metric 的 counter 存在哪里。

  1. 首先排除日志分析程序,因为它只管分析和推送,不负责计算。
  2. 查看 pushgateway 的启动参数,发现 persistence.filepersistence.interval 这两个参数与这个问题相关,他们分别表示 pushgateway 存储 metrics 文件路径和同步周期,如果没有设置 persistence.file 参数,那么默认存在内存中。

我马上检查了下给用户服务的 pushgateway 启动方式,发现果然缺少 persistence.file 参数,再看看进程重启时间,恰好发生在置零之前一点,瞬间恍然大悟。

好了,问题终于水露石出,简单总结就是:

我们选用了 counter 的 delta 来算每日统计量,然后因为 pushgateway 启动方式不对,没有开启持久化选项,导致在 pushgateway 重启过后, metric 丢失。当新数据推送来的时候,pushgateway 会重新生成一个 counter, 故从零开始。


那是不是添加了持久化选项,你的 counter 就能确保一直递增呢?

答案当然是否定的。

因为出于性能的考虑,它的持久化是异步落盘的,这就意味着在重启的时候,也会丢失数据,出现 delta 为负数的情况也有可能的,只不过出现的条件与 pushgateway 落盘时间和 Prometheus 最后拉取数据,以及 Prometheus 拉取到数据落盘时间都有关系。

虽然我们不能确保 counter 一直递增,但是我觉得我们用 delta 来计算每日统计还是很合理的,因为:

  1. 对于时刻都有很多人访问的接口,即使出现 pushgateway 出现重启,丢数据的情况,(不会出现置零的情况,它会从上一个落盘的时刻累加)从一日的统计区间来看它也应该是增加的。
  2. 我们统计只是用于看一天的大致体量,用于查看是否出现数量级异常,并不一定需要非常精确。
  3. 我们可以增加服务的可靠性,减少服务重启次数等。

写在最后:

通过这次问题排查,让我对 Prometheus 有了更深的认识,对 Prometheus 而言,它不维护 metric 的结构,比如 counter, 像平时我们用 PromQL 查询用到的数据,只是一个个 float64 数字而已。

Prometheus 出于性能的考虑,不太适合用来做审计,计费相关的业务,使用 delta 来看数量级的趋势还是很不错的。