4. PromQL计算SLO达成率:基于Histogram与Counter
SLO达成率是误差预算台账的核心输入指标,它直接反映服务在过去一个合规窗口内是否满足了用户承诺。Prometheus提供了两种主要的指标类型来支撑SLO计算:Histogram(直方图)用于衡量延迟分布,Counter(计数器)用于衡量错误计数。本章将给出标准化的PromQL表达式,并解释其数学原理与适用场景。
4.1 基于Histogram的延迟SLO达成率
对于延迟类SLO(例如“99%的请求延迟低于200ms”),我们需要计算在滑动时间窗口内,满足延迟阈值的请求占总请求的比例。Histogram桶天然支持这种分位数估算。
4.1.1 核心PromQL表达式
# 延迟SLO达成率(基于Histogram)
(
sum(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
) * 100
表达式拆解:
| 组件 | 含义 |
|---|---|
le="0.2" |
延迟阈值桶,表示所有延迟 ≤ 0.2秒的请求 |
rate(...[5m]) |
5分钟滑动窗口内的每秒速率 |
sum(...) |
聚合所有实例/分片,得到全局速率 |
..._count |
总请求数桶,等价于 le="+Inf" |
最佳实践: 如果服务有多个实例,务必使用
sum 聚合所有实例的速率,否则单个实例的局部达成率无法代表全局SLO状态。
4.1.2 多阈值SLO达成率(复合条件)
当SLO包含多个延迟阈值(例如“95%请求 < 100ms,99%请求 < 500ms”)时,需要分别计算每个桶的达成率,然后取最小值作为最终达成率:
# 多阈值SLO达成率(取最小值)
min(
(
sum(rate(http_request_duration_seconds_bucket{le="0.1"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
) * 100,
(
sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
) * 100
)
注意: 使用
min 取最差桶的达成率,是因为SLO承诺的是“所有阈值同时满足”。如果100ms桶达成率是96%,但500ms桶达成率只有98%,则整体SLO达成率应为96%(最差情况)。
4.2 基于Counter的错误SLO达成率
对于可用性类SLO(例如“99.9%的请求成功”),我们直接使用错误计数器与总请求计数器计算错误率,再转换为达成率。
4.2.1 核心PromQL表达式
# 错误SLO达成率(基于Counter)
(
1 -
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
)
) * 100
表达式拆解:
| 组件 | 含义 |
|---|---|
status=~"5.." |
匹配所有5xx状态码(服务端错误) |
rate(...[5m]) |
5分钟滑动窗口内的每秒错误速率 |
1 - (错误率) |
将错误率转换为成功率(达成率) |
关键区别: 错误SLO的达成率计算不需要Histogram桶,直接使用Counter的速率比值即可。但要注意错误分类的粒度——是否包含4xx?通常4xx是客户端错误,不计入SLO错误预算。
4.3 滑动窗口与合规窗口对齐
PromQL中的 [5m] 是滑动窗口大小,它必须与SLO的合规窗口对齐。常见的对齐规则如下:
| SLO合规窗口 | 推荐PromQL窗口 | 说明 |
|---|---|---|
| 30天滚动 | [30d] |
直接使用30天窗口,但注意Prometheus存储限制 |
| 28天滚动 | [28d] |
常见于月度SLO报告 |
| 7天滚动 | [7d] |
周级SLO,适合快速反馈 |
| 1小时滑动 | [1h] |
实时仪表盘常用,用于告警 |
存储警告: 使用
[30d] 窗口时,Prometheus需要保留30天的原始数据。如果数据保留期不足,查询会返回空或错误。建议在SLO计算前先确认 --storage.tsdb.retention.time 配置。
4.4 标准化SLO达成率计算流程
4.5 常见陷阱与避坑指南
- 桶边界包含性: Histogram桶
le="0.2"包含 ≤0.2秒的请求,但不包含 >0.2秒的请求。确保阈值桶与SLO定义一致。 - 速率 vs 原始计数: 始终使用
rate()或increase(),不要直接对原始Counter做除法,否则在实例重启或Scrape间隔不均时会产生偏差。 - 聚合维度: 如果SLO是按服务级别定义的,必须
sum所有实例;如果SLO是按集群或区域定义的,需要按对应标签聚合。 - 空窗口处理: 当窗口内无请求时,
rate(count)为0,除法结果为NaN。建议使用or vector(100)兜底,表示“无请求时默认达成率100%”。
# 带NaN兜底的延迟SLO达成率
(
sum(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
) * 100
or vector(100)
生产建议: 将上述PromQL表达式封装为Recording Rule,每30秒计算一次并写入新指标
slo_latency_achievement_ratio。这样在仪表盘和告警中直接引用该指标,避免每次查询都重复计算大窗口聚合,大幅降低Prometheus查询压力。
4.6 多指标联合SLO达成率
当SLO同时包含延迟和错误约束时(例如“99%请求延迟 < 200ms 且 99.9%请求成功”),需要计算联合达成率:
# 联合SLO达成率(取最小值)
min(
# 延迟SLO达成率
(
sum(rate(http_request_duration_seconds_bucket{le="0.2"}[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
) * 100,
# 错误SLO达成率
(
1 -
(
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
)
) * 100
)
or vector(100)
联合达成率取两个维度中的最小值,因为SLO承诺的是“所有条件同时满足”。任何一个维度未达标,整体SLO即视为未达成。