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达成率计算流程

SLO达成率计算流程 原始指标 SLO定义 合规窗口 判断SLO类型:延迟类 vs 错误类 延迟SLO:使用Histogram rate(le桶) / rate(count) 错误SLO:使用Counter 1 - rate(错误) / rate(总数) 输出: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即视为未达成。