注意: 本文档早于原生直方图(在 Prometheus v2.40 中作为实验性功能添加)。一旦原生直方图更接近成为稳定功能,本文档将进行彻底更新。

直方图和摘要

直方图和摘要是更复杂的指标类型。单个直方图或摘要不仅会创建大量时间序列,而且更难以正确使用这些指标类型。本节帮助你选择和配置适合你的用例的指标类型。

库支持

首先,查看你的库是否支持直方图摘要

一些库只支持这两种类型中的一种,或者它们对摘要的支持有限(缺少分位数计算)。

观测计数和总和

直方图和摘要都对观测值进行采样,通常是请求持续时间或响应大小。它们跟踪观测值的数量 *和* 观测值的总和,允许你计算观测值的 *平均值*。请注意,观测值的数量(在 Prometheus 中显示为带有 `_count` 后缀的时间序列)本质上是一个计数器(如上所述,它只会增加)。观测值的总和(显示为带有 `_sum` 后缀的时间序列)也像一个计数器一样,只要没有负数观测值。显然,请求持续时间或响应大小永远不会是负数。但是,原则上,你可以使用摘要和直方图来观测负值(例如,摄氏温度)。在这种情况下,观测值的总和可能会下降,因此你不能再对其应用 `rate()` 函数。在极少数情况下,你需要应用 `rate()` 函数并且无法避免负数观测值,你可以使用两个单独的摘要,一个用于正数观测值,一个用于负数观测值(后者使用反号),然后在以后使用合适的 PromQL 表达式组合结果。

要从名为 `http_request_duration_seconds` 的直方图或摘要中计算过去 5 分钟内的平均请求持续时间,请使用以下表达式

  rate(http_request_duration_seconds_sum[5m])
/
  rate(http_request_duration_seconds_count[5m])

Apdex 分数

直方图(而不是摘要)的一个直接用途是计算落入特定观测值桶中的观测值数量。

你可能有一个 SLO,要求在 300 毫秒内服务 95% 的请求。在这种情况下,配置一个直方图,使其桶的上限为 0.3 秒。然后,你可以直接表示在 300 毫秒内服务的请求的相对数量,并在该值低于 0.95 时轻松发出警报。以下表达式按任务计算过去 5 分钟内服务的请求的该值。请求持续时间是用名为 `http_request_duration_seconds` 的直方图收集的。

  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)
/
  sum(rate(http_request_duration_seconds_count[5m])) by (job)

你可以用类似的方式近似计算众所周知的 Apdex 分数。配置一个桶,以目标请求持续时间作为上限,再配置另一个桶,以可容忍的请求持续时间(通常是目标请求持续时间的 4 倍)作为上限。示例:目标请求持续时间为 300 毫秒。可容忍的请求持续时间为 1.2 秒。以下表达式计算过去 5 分钟内每个任务的 Apdex 分数

(
  sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m])) by (job)
+
  sum(rate(http_request_duration_seconds_bucket{le="1.2"}[5m])) by (job)
) / 2 / sum(rate(http_request_duration_seconds_count[5m])) by (job)

请注意,我们除以两个桶的总和。原因是直方图桶是累积的。`le="0.3"` 桶也包含在 `le="1.2"` 桶中;除以 2 可以对此进行校正。

该计算并不完全符合传统的 Apdex 分数,因为它在计算的满意和可容忍部分中包含了误差。

分位数

你可以使用摘要和直方图来计算所谓的 φ-分位数,其中 0 ≤ φ ≤ 1。φ-分位数是在 N 个观测值中排名第 φ*N 的观测值。φ-分位数的示例:0.5-分位数被称为中位数。0.95-分位数是第 95 百分位数。

摘要和直方图之间的本质区别在于,摘要在客户端计算流式 φ-分位数并直接暴露它们,而直方图暴露分桶的观测计数,并且从直方图的桶中计算分位数发生在服务端,使用 histogram_quantile() 函数

这两种方法有许多不同的含义

直方图 摘要
必要配置 选择适合预期观测值范围的桶。 选择所需的 φ-分位数和滑动窗口。其他 φ-分位数和滑动窗口以后无法计算。
客户端性能 观测非常廉价,因为它们只需要递增计数器。 由于流式分位数计算,观测成本很高。
服务端性能 服务端必须计算分位数。如果临时计算时间过长(例如,在大型仪表盘中),你可以使用记录规则 低服务端成本。
时间序列的数量(除了 _sum_count 系列之外) 每个配置的桶一个时间序列。 每个配置的分位数一个时间序列。
分位数误差(详见下文) 误差在观测值维度上受到相关桶宽度的限制。 误差在 φ 维度上受到可配置值的限制。
φ-分位数和滑动时间窗口的规范 使用 Prometheus 表达式临时指定。 由客户端预先配置。
聚合 使用 Prometheus 表达式临时指定。 通常不可聚合

请注意表格中最后一项的重要性。让我们回到在 300 毫秒内服务 95% 请求的 SLO。这一次,你不想显示在 300 毫秒内服务的请求的百分比,而是第 95 百分位数,即你已服务 95% 请求的请求持续时间。为此,你可以配置一个摘要,其中包含 0.95-分位数和(例如)5 分钟的衰减时间,或者你可以配置一个直方图,其中包含 300 毫秒标记附近的几个桶,例如 {le="0.1"}{le="0.2"}{le="0.3"}{le="0.45"}。如果你的服务以多个实例复制运行,你将从每个实例收集请求持续时间,然后你想要将所有内容聚合为总体第 95 百分位数。但是,聚合摘要中预先计算的分位数很少有意义。在这种特殊情况下,平均分位数会产生统计上无意义的值。

avg(http_request_duration_seconds{quantile="0.95"}) // BAD!

使用直方图,可以使用 histogram_quantile() 函数完美地进行聚合。

histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) // GOOD.

此外,如果你的 SLO 发生变化,现在你想绘制第 90 百分位数,或者你想考虑过去 10 分钟而不是过去 5 分钟,你只需要调整上面的表达式,而不需要重新配置客户端。

分位数估计的误差

分位数,无论是客户端计算还是服务端计算,都是估计值。重要的是要理解这种估计的误差。

继续上面的直方图示例,想象一下你的通常请求持续时间几乎都非常接近 220 毫秒,或者换句话说,如果你可以绘制“真实”的直方图,你会看到一个非常尖锐的峰值在 220 毫秒处。在上面配置的 Prometheus 直方图指标中,几乎所有观测值,因此也包括第 95 百分位数,都将落入标记为 {le="0.3"} 的桶中,即从 200 毫秒到 300 毫秒的桶。直方图实现保证真实的第 95 百分位数在 200 毫秒到 300 毫秒之间。为了返回单个值(而不是区间),它应用线性插值,在这种情况下产生 295 毫秒。计算的分位数给你留下了你接近突破 SLO 的印象,但实际上,第 95 百分位数略高于 220 毫秒,距离你的 SLO 还很远。

我们思想实验的下一步:后端路由的更改为所有请求持续时间增加了 100 毫秒的固定量。现在请求持续时间在 320 毫秒处出现尖峰,几乎所有观测值都将落入从 300 毫秒到 450 毫秒的桶中。第 95 百分位数计算为 442.5 毫秒,尽管正确的值接近 320 毫秒。虽然你只稍微超出 SLO,但计算出的第 95 分位数看起来要糟糕得多。

摘要本可以在两种情况下都毫无问题地计算出正确的百分位数值,至少如果它在客户端使用了合适的算法(例如 Go 客户端使用的算法)。不幸的是,如果你需要聚合来自多个实例的观测值,则不能使用摘要。

幸运的是,由于你对桶边界的适当选择,即使在这个观测值分布中非常尖锐的峰值的虚构示例中,直方图也能够正确识别你是否在 SLO 内或 SLO 外。此外,分位数的实际值越接近我们的 SLO(或者换句话说,我们实际最感兴趣的值),计算值就越准确。

现在让我们再次修改实验。在新的设置中,请求持续时间的分布在 150 毫秒处有一个峰值,但它不像以前那么尖锐,只占观测值的 90%。10% 的观测值均匀分布在 150 毫秒到 450 毫秒之间的长尾中。在那种分布下,第 95 百分位数恰好在我们的 300 毫秒 SLO 处。使用直方图,计算值是准确的,因为第 95 百分位数的值恰好与其中一个桶边界重合。即使是略有不同的值仍然是准确的,因为相关桶内的(人为的)均匀分布正是桶内线性插值所假设的。

现在,摘要报告的分位数的误差变得更加有趣了。摘要中分位数的误差是在 φ 维度上配置的。在我们的例子中,我们可能配置了 0.95±0.01,即计算值将在第 94 百分位数和第 96 百分位数之间。在上述分布下,第 94 分位数为 270 毫秒,第 96 分位数为 330 毫秒。摘要报告的第 95 百分位数的计算值可能在 270 毫秒到 330 毫秒之间的任何位置,不幸的是,这完全是在 SLO 内与 SLO 外之间的差异。

底线是:如果你使用摘要,你可以控制 φ 维度上的误差。如果你使用直方图,你可以控制观测值维度上的误差(通过选择合适的桶布局)。对于广泛的分布,φ 的微小变化会导致观测值的较大偏差。对于尖锐的分布,观测值的小区间覆盖了 φ 的大区间。

两个经验法则

  1. 如果你需要聚合,请选择直方图。

  2. 否则,如果你对将要观测的值的范围和分布有所了解,请选择直方图。如果你需要精确的分位数,无论值的范围和分布是什么,请选择摘要。

如果我的客户端库不支持我需要的指标类型,我该怎么办?

自己实现它! 欢迎代码贡献。一般来说,我们预计直方图比摘要更迫切需要。直方图也更容易在客户端库中实现,因此如果 doubt,我们建议首先实现直方图。

本文档是开源的。请通过提交 issue 或 pull request 来帮助改进它。