NOTE: 本文档撰写于原生 histogram 出现之前(原生 histogram 作为实验性功能在 Prometheus v2.40 中添加,并在 v3.8 中变为稳定版)。我们计划在不久的将来对本文档进行全面更新。
Histogram 和 summary 是更复杂的指标类型。不仅单个 histogram 或 summary 会创建大量的时间序列,正确使用这些指标类型也更加困难。本节帮助你为你的用例选择和配置适当的指标类型。
库支持
首先,检查你使用的库对 histogram 和 summary 的支持情况。
有些库只支持其中一种类型,或者只提供有限的 summary 支持(缺少分位数计算功能)。
观测值的计数与总和
Histogram 和 summary 都对观测值进行采样,通常是请求持续时间或响应大小。它们跟踪观测值的数量和观测值的总和,使你能够计算观测值的平均值。请注意,观测值的数量(在 Prometheus 中以带 _count 后缀的时间序列出现)本质上是一个 counter(如上所述,它只会上升)。观测值的总和(以带 _sum 后缀的时间序列出现)也表现为 counter,只要没有负值观测。显然,请求持续时间或响应大小永远不会是负值。然而,原则上你可以使用 summary 和 histogram 来观测负值(例如摄氏度温度)。在这种情况下,观测值的总和可能会下降,因此你不能再对其应用 rate()。在那些你需要应用 rate() 且无法避免负值观测的罕见情况下,你可以使用两个独立的 summary,一个用于正值观测,一个用于负值观测(后者使用相反的符号),然后使用适当的 PromQL 表达式组合结果。
要计算名为 http_request_duration_seconds 的 histogram 或 summary 在过去 5 分钟内的平均请求持续时间,请使用以下表达式:
rate(http_request_duration_seconds_sum[5m])
/
rate(http_request_duration_seconds_count[5m])
Apdex 分数
Histogram(而非 summary)的一个直接用途是计算落入特定观测值桶中的观测数量。
你可能有一个 SLO 要求在 300ms 内服务 95% 的请求。在这种情况下,配置一个上限为 0.3 秒的桶的 histogram。然后你可以直接表示在 300ms 内服务的请求的相对比例,并在值低于 0.95 时轻松发出告警。以下表达式按 job 计算过去 5 分钟内服务的请求。请求持续时间使用名为 http_request_duration_seconds 的 histogram 收集。
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 倍)为上限的桶。示例:目标请求持续时间为 300ms,可容忍请求持续时间为 1.2s。以下表达式产生过去 5 分钟内每个 job 的 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)
请注意,我们对两个桶的总和进行了除法。原因是 histogram 的桶是累积的。le="0.3" 桶也包含在 le="1.2" 桶中;除以 2 可以修正这一点。
该计算与传统的 Apdex 分数并不完全一致,因为它在满意和可容忍部分的计算中包含了错误请求。
分位数
你可以使用 summary 和 histogram 来计算所谓的 φ 分位数,其中 0 ≤ φ ≤ 1。φ 分位数是在 N 个观测值中排名第 φ*N 的观测值。φ 分位数的示例:0.5 分位数即中位数。0.95 分位数即第 95 百分位数。
Summary 和 histogram 之间的本质区别在于,summary 在客户端计算流式 φ 分位数并直接暴露,而 histogram 暴露分桶的观测计数,从 histogram 的桶计算分位数在服务端使用 histogram_quantile() 函数完成。
这两种方法有许多不同的影响:
| Histogram | Summary | |
|---|---|---|
| 所需配置 | 选择适合预期观测值范围的桶。 | 选择所需的 φ 分位数和滑动窗口。之后无法计算其他 φ 分位数和滑动窗口。 |
| 客户端性能 | 观测值开销极小,因为只需递增计数器。 | 由于流式分位数计算,观测值开销较大。 |
| 服务端性能 | 服务器需要计算分位数。如果临时计算耗时过长(例如在大型仪表盘中),可以使用记录规则。 | 服务端开销低。 |
时间序列数量(除 _sum 和 _count 序列外) |
每个配置的桶一个时间序列。 | 每个配置的分位数一个时间序列。 |
| 分位数误差(详见下文) | 误差在观测值维度上受相关桶的宽度限制。 | 误差在 φ 维度上受可配置值限制。 |
| φ 分位数和滑动时间窗口的指定 | 使用 Prometheus 表达式临时指定。 | 由客户端预配置。 |
| 聚合 | 使用 Prometheus 表达式临时聚合。 | 通常不可聚合。 |
请注意表格中最后一项的重要性。让我们回到在 300ms 内服务 95% 请求的 SLO。这次,你不想显示在 300ms 内服务的请求百分比,而是想显示第 95 百分位数,即你服务了 95% 请求的请求持续时间。要做到这一点,你可以配置一个 0.95 分位数和(例如)5 分钟衰减时间的 summary,或者配置一个在 300ms 附近有几个桶的 histogram,例如 {le="0.1"}、{le="0.2"}、{le="0.3"} 和 {le="0.45"}。如果你的服务有多个副本实例运行,你将从每一个实例收集请求持续时间,然后将所有数据聚合为一个总体的第 95 百分位数。然而,聚合 summary 预计算的分位数几乎没有意义。在这种特定情况下,对分位数取平均值会产生统计上无意义的值。
avg(http_request_duration_seconds{quantile="0.95"}) // 错误做法!
使用 histogram,通过 histogram_quantile() 函数可以完美地进行聚合。
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) // 正确做法。
此外,如果你的 SLO 发生变化,你现在想绘制第 90 百分位数,或者你想考虑过去 10 分钟而不是过去 5 分钟,你只需要调整上面的表达式,而不需要重新配置客户端。
分位数估算的误差
分位数,无论是在客户端还是服务端计算的,都是估算值。理解这种估算的误差很重要。
延续上面的 histogram 示例,假设你通常的请求持续时间几乎都非常接近 220ms,换句话说,如果你能绘制"真实"的 histogram,你会在 220ms 处看到一个非常尖锐的峰值。在上面配置的 Prometheus histogram 指标中,几乎所有的观测值(因此也包括第 95 百分位数)都会落入标记为 {le="0.3"} 的桶中,即从 200ms 到 300ms 的桶。Histogram 的实现保证真实的第 95 百分位数在 200ms 和 300ms 之间的某个位置。为了返回单个值(而不是区间),它应用线性插值,在这种情况下得出 295ms。计算出的分位数给你的印象是你接近违反 SLO,但实际上,第 95 百分位数只是略高于 220ms,与你的 SLO 有相当舒适的距离。
接下来是我们的思想实验:后端路由的变化给所有请求持续时间增加了固定的 100ms。现在请求持续时间在 320ms 处有尖锐的峰值,几乎所有观测值都将落入从 300ms 到 450ms 的桶中。第 95 百分位数被计算为 442.5ms,尽管正确的值接近 320ms。虽然你只是略微超出了 SLO,但计算出的第 95 分位数看起来要糟糕得多。
在这两种情况下,summary 都不会在计算正确的百分位值时遇到问题,至少在客户端使用适当的算法(如 Go 客户端使用的算法)的情况下是这样。不幸的是,如果你需要聚合来自多个实例的观测值,就不能使用 summary。
幸运的是,由于你对桶边界的适当选择,即使在这个观测值分布非常尖锐的人为示例中,histogram 也能正确识别你是否在 SLO 之内或之外。此外,分位数的实际值越接近我们的 SLO(换句话说,我们真正最感兴趣的值),计算值就越准确。
现在让我们再次修改实验。在新的设置中,请求持续时间的分布在 150ms 处有一个峰值,但不像之前那么尖锐,只包含 90% 的观测值。10% 的观测值均匀分布在 150ms 到 450ms 之间的长尾中。在这种分布下,第 95 百分位数恰好在我们 300ms 的 SLO 处。使用 histogram,计算值是准确的,因为第 95 百分位数的值恰好与桶边界之一重合。即使是略有不同的值也仍然是准确的,因为相关桶内的(人为的)均匀分布正是桶内线性插值所假设的。
Summary 报告的分位数误差现在变得更有趣了。Summary 中分位数的误差在 φ 维度上配置。在我们的例子中,我们可能配置了 0.95±0.01,即计算值将在第 94 和第 96 百分位数之间。上述分布的第 94 分位数是 270ms,第 96 分位数是 330ms。Summary 报告的第 95 百分位数的计算值可以在 270ms 和 330ms 之间的任何位置,不幸的是,这足以构成明确在 SLO 内与明确超出 SLO 之间的全部差异。
底线是:如果你使用 summary,你在 φ 维度上控制误差。如果你使用 histogram,你在观测值维度上控制误差(通过选择适当的桶布局)。在分布较宽的情况下,φ 的微小变化会导致观测值的大幅偏差。在分布尖锐的情况下,较小的观测值区间覆盖了较大的 φ 区间。
两条经验法则:
如果你需要聚合,选择 histogram。
否则,如果你对将要观测的值的范围和分布有一定了解,选择 histogram。如果你需要精确的分位数,而不管值的范围和分布如何,选择 summary。
如果我的客户端库不支持我需要的指标类型怎么办?
实现它!欢迎贡献代码。一般来说,我们预期 histogram 比 summary 更为迫切需要。Histogram 在客户端库中的实现也更容易,因此如果有疑问,我们建议首先实现 histogram。