KernelRanch
返回文章列表

多实例接口的 p95 怎么算?先合并 Prometheus 直方图的桶

2026-11-13 #prometheus#可观测性#metrics

服务级 p95 应从服务级请求分布计算。使用 Prometheus 经典直方图时,对每条累积桶计数先取 rate(),按服务与 le 聚合,再交给 histogram_quantile()。实例 p95 的平均值会把低流量实例的尾延迟当成和主实例同等重要,即使加权平均也无法从各实例分位数恢复总体分位数。

1. 用 1000 个合成请求验证差异

保存为 histogram_p95_lab.py 并运行。假设每组桶计数来自同一个固定窗口。样本从零起算,含三个有限桶和 +Inf 桶,数值单位为秒。函数只实现目标分位数落在有限桶内的线性插值,以复算文档中的计算规则。

from math import inf, isclose

bounds = (0.1, 0.3, 1.0, inf)
fast = (0, 980, 990, 990)
slow = (0, 0, 10, 10)


def estimate_p95(cumulative):
    rank = 0.95 * cumulative[-1]
    lower_bound, previous = 0.0, 0
    for upper_bound, count in zip(bounds, cumulative):
        if count >= rank:
            if upper_bound == inf or count == previous:
                raise ValueError("sample requires a populated finite bucket")
            return lower_bound + (upper_bound - lower_bound) * (
                (rank - previous) / (count - previous)
            )
        lower_bound, previous = upper_bound, count
    raise ValueError("invalid cumulative counts")


merged = tuple(a + b for a, b in zip(fast, slow))
fast_p95, slow_p95 = estimate_p95(fast), estimate_p95(slow)
combined_p95 = estimate_p95(merged)
print("merged buckets:", merged)
print(f"instance p95s: {fast_p95:.4f}s, {slow_p95:.4f}s")
print(f"mean of instance p95s: {(fast_p95 + slow_p95) / 2:.4f}s")
print(f"merged p95 estimate: {combined_p95:.4f}s")
print(f"at or below 300ms: {merged[1] / merged[-1]:.1%}")
assert isclose(combined_p95, 0.1 + 0.2 * 950 / 980)
assert merged[1] / merged[-1] == 0.98
merged buckets: (0, 980, 1000, 1000)
instance p95s: 0.2919s, 0.9650s
mean of instance p95s: 0.6285s
merged p95 estimate: 0.2939s
at or below 300ms: 98.0%

2. 从计算对应到 PromQL

假设指标单位为秒,所有实例的桶边界一致,且 service 标签标识服务。经典直方图查询如下。

histogram_quantile(
  0.95,
  sum by (service, le) (rate(http_request_duration_seconds_bucket[5m]))
)

rate() 放在 sum 内,先让 Prometheus 按原始序列处理计数器重置。查询还须保留 le,供分位数函数识别桶的上界。若希望按路由观察,在埋点提供稳定的路由模板标签,并把它加入聚合维度。

判断“有多少请求在 300 ms 内完成”时,若所有实例都设有该边界,可以直接用该桶的速率除以总请求速率。例如 le 的实际标签值为 0.3 时可用下式。无请求时的比率缺乏分母,告警逻辑应显式处理零流量窗口。

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

合并结果的 300 ms 达标率来自累积桶计数。p95 落在 100–300 ms 桶时,需要假设桶内分布并插值,估算精度取决于桶边界。变更桶边界期间,需确认参与同一聚合的实例是否仍使用一致的分布定义。

小结

服务级分位数先按实例合并桶。固定阈值的达标率在对应桶存在时,可直接从桶和总数计算。

数据来源与检索时间

来源URL检索日期对应内容
Prometheus histogram_quantilehttps://prometheus.io/docs/prometheus/latest/querying/functions/#histogram_quantile2026-11-13经典桶聚合、le 和线性插值
Prometheus ratehttps://prometheus.io/docs/prometheus/latest/querying/functions/#rate2026-11-13处理计数器重置和先 rate 后聚合
Prometheus 直方图与摘要https://prometheus.io/docs/practices/histograms/2026-11-13分位数聚合与固定阈值达标率

核验说明

  • Windows、Python 3.11.9 对合成桶计数复算并验证关键结果。两条 PromQL 按 Prometheus 官方文档核对,未在 Prometheus 服务实例执行。