SLO 工程实践
学习目标
掌握从 SLI 定义 → SLO 设定 → 错误预算策略 → 告警规则 → Dashboard 设计的完整 SLO 工程链路。
1. SLI 定义方法论
1.1 好的 SLI 的三个标准
| 标准 | 说明 | 反例 |
|---|
| 用户可见 | 用户直接感受到 | CPU使用率(用户不直接感知) |
| 可测量 | Prometheus/日志能采集 | ”用户体验好”(无法量化) |
| 可行动 | 变差时能触发行动 | ”代码行数”(无关可靠性) |
1.2 SLI 四类黄金信号
可用性 (Availability) → "服务能正常响应吗?"
延迟 (Latency) → "服务响应快吗?"
流量 (Traffic) → "服务被用得多吗?"
错误 (Errors) → "多少请求失败了?"
饱和 (Saturation) → "服务快到极限了吗?"
1.3 按服务类型选 SLI
| 服务类型 | 核心 SLI | PromQL 示例 |
|---|
| HTTP API | 可用性、P99延迟、错误率 | 见下方 |
| gRPC | 可用性、P99延迟 | grpc_server_handled_total |
| 消息队列 | 消费延迟、积压量 | kafka_consumer_lag |
| 数据库 | 查询延迟、连接池利用率 | pg_stat_activity |
| 定时任务 | 成功率、执行时长 | 自建指标 |
1.4 PromQL 实现常用 SLI
# === 可用性 (Availability) ===
# HTTP API 成功率(排除 5xx)
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# === 延迟 (Latency) ===
# P99 响应时间
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# P50/P95/P99 三线合一
histogram_quantile(0.50, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# === 错误率 (Error Rate) ===
# 5xx 占比(按 endpoint 分组)
sum(rate(http_requests_total{status=~"5.."}[5m])) by (path)
/
sum(rate(http_requests_total[5m])) by (path)
# === 饱和度 (Saturation) ===
# Goroutine 数量(Go 服务)
go_goroutines{job="my-service"}
# 线程池利用率
thread_pool_active_threads / thread_pool_max_threads
2. SLO 设定
2.1 SLO 黄金公式
SLO = SLI ≥ 目标值,在 测量窗口 内
例:99.9% 的请求在 30 天内 P99 < 300ms
2.2 可用性 SLO 对照表
| SLO | 月度不可用时间 | 年度不可用时间 | 适用场景 |
|---|
| 99% | 7h 18m | 3d 15h | 内部工具 |
| 99.9% | 43m | 8h 45m | 业务后台 |
| 99.95% | 21m | 4h 22m | 支付/交易 |
| 99.99% | 4m | 52m | 核心基础设施 |
2.3 按重要性设定 SLO
# 例:一个电商系统的 SLO 矩阵
services:
- name: 用户登录
tier: critical
slo:
availability: 99.95% # 月度
latency_p99: 500ms
- name: 商品列表
tier: important
slo:
availability: 99.9%
latency_p99: 800ms
- name: 推荐算法
tier: best_effort
slo:
availability: 99%
latency_p99: 2000ms
2.4 SLO 设定的常见陷阱
| 陷阱 | 说明 | 正确做法 |
|---|
| 所有服务 99.99% | 过度承诺,不可持续 | 按 tier 分级 |
| 只看平均值 | P50 掩盖长尾 | 必须用 P95/P99 |
| 窗口太短 | 5分钟窗口波动大 | 至少 28 天滚动 |
| 忽略部分失败 | 只看整体成功率 | 按 endpoint 拆分 |
| 无降级目标 | 全有或全无 | 定义 degraded 状态 |
3. 错误预算
3.1 核心公式
错误预算 = 1 - SLO
如果 SLO 是 99.9% 可用性:
错误预算 = 0.1% = 每月允许 43 分钟不可用
3.2 错误预算消耗计算
# 错误预算消耗率(当前烧钱速度 vs 预算)
# 假设 SLO = 99.9%(30天窗口)
# 1. 当前错误率
error_rate = sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
# 2. 错误预算剩余(百分比)
budget_remaining = (1 - error_rate) - 0.999
# 3. 错误预算消耗速率(按当前速率多久烧完预算)
# burn rate < 1: 正常
# burn rate > 1: 在消耗预算(当月会有短缺)
# burn rate > 10: 紧急!几小时内烧完
burn_rate = (error_rate / (1 - 0.999))
3.3 错误预算策略
错误预算 > 50% 剩余:
→ 正常节奏发布,功能上线不受限
错误预算 20%-50%:
→ 减少发布频率,增加变更评审
错误预算 < 20%:
→ 冻结所有非紧急发布
→ 全员投入可靠性改进
错误预算耗尽(≤ 0):
→ 发布完全冻结
→ 启动可靠性专项
→ 需要 VP 级别审批才能破例
4. Multi-Window Multi-Burn-Rate 告警
4.1 核心原理
Google SRE 提出的告警策略:用两个窗口 + 两种燃烧速率检测不同程度的问题。
短窗口 + 高燃速 → 紧急告警(page)
长窗口 + 低燃速 → 警告(ticket)
目的:避免单一窗口的误报,同时不错过真实故障
4.2 标准配置
| 告警级别 | 短窗口 | 短窗口燃速 | 长窗口 | 长窗口燃速 | 通知 |
|---|
| Critical | 1h | 14.4x | 5m | 14.4x | PagerDuty/电话 |
| Warning | 6h | 6x | 30m | 6x | Slack/Ticket |
| Info | 3d | 1x | — | — | Dashboard |
4.3 Prometheus 告警规则
# prometheus-rules/slo-alerts.yaml
groups:
- name: slo_alerts
interval: 30s
rules:
# === Critical: 1h 内燃烧了 2% 的错误预算 ===
- alert: SLOBurnRateCritical
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) > 14.4 * (1 - 0.999)
for: 5m
labels:
severity: critical
slo: "99.9%"
annotations:
summary: "错误预算高速燃烧!1h burn rate > 14.4x"
description: "当前 1h 错误率 {{ $value | humanizePercentage }},
预算燃烧速率远超正常值,2h 内可能耗尽月度预算"
# === Warning: 6h 低燃速预热 ===
- alert: SLOBurnRateWarning
expr: |
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
) > 6 * (1 - 0.999)
for: 30m
labels:
severity: warning
slo: "99.9%"
annotations:
summary: "错误预算在持续消耗,6h burn rate > 6x"
description: "建议减少发布并排查根因"
# === 错误预算耗尽 ===
- alert: SLOBurnRateExhausted
expr: |
(
1 - (
sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
)
) > (1 - 0.999)
for: 5m
labels:
severity: critical
annotations:
summary: "30天错误预算已耗尽!"
description: "当前 30d 可用性低于 SLO=99.9%,立即冻结发布"
5. SLO Dashboard 设计
5.1 Grafana Dashboard 布局
┌─────────────────────────────────────────────────────┐
│ SLO Overview — Service: api-gateway │
├──────────────┬──────────────┬──────────────────────┤
│ 可用性 SLO │ 延迟 SLO │ 错误预算 │
│ 99.95% ✅ │ P99<300ms ✅ │ 剩余 76% 🟡 │
│ (本月 99.97%)│ (本月 287ms) │ │
├──────────────┴──────────────┴──────────────────────┤
│ │
│ 📈 可用性趋势(30天) │
│ 99.98% ┤ ╭─ │
│ 99.95% ┤──────────────── SLO ───────────────── │
│ 99.90% ┤ ╲ ╱ │
│ └──────────────────────────── │
│ │
│ 📊 错误预算燃烧仪表盘 │
│ [████████████░░░░] 76% 剩余 (8.7h / 36h) │
│ │
│ 🔥 Burn Rate(当前) │
│ 1h: 0.3x 🟢 | 6h: 0.5x 🟢 | 24h: 1.2x 🟡 │
│ │
│ 📋 按 Endpoint 的可用性 │
│ /api/users ████████████ 99.98% ✅ │
│ /api/orders ██████████░░ 99.91% ⚠️ (低于 SLO) │
│ /api/search ████████████ 99.99% ✅ │
│ │
│ 📜 最近 SLO 事件 │
│ 05-21 14:30 Error budget < 20% → 冻结非紧急发布 │
│ 05-20 09:15 SLO burn rate warning (6h: 8.2x) │
│ 05-18 22:00 P99 latency spike → 自动回滚 │
└─────────────────────────────────────────────────────┘
5.2 Grafana 面板 PromQL
# === 错误预算剩余(Gauge 面板)===
1 - (
(1 - (
sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
))
/
(1 - 0.999)
)
# === 月度可用性趋势(Time Series)===
# 按天聚合,看趋势
sum(rate(http_requests_total{status!~"5.."}[1d]))
/
sum(rate(http_requests_total[1d]))
# === 按 endpoint 可用性(Table)===
sum(rate(http_requests_total{status!~"5.."}[7d])) by (path)
/
sum(rate(http_requests_total[7d])) by (path)
# === Burn Rate Multi-window(Stat)===
# 1h burn rate
(
sum(rate(http_requests_total{status=~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
) / (1 - 0.999)
# 6h burn rate
(
sum(rate(http_requests_total{status=~"5.."}[6h]))
/
sum(rate(http_requests_total[6h]))
) / (1 - 0.999)
6. 实战:为已有服务建立 SLO 体系
步骤
# 第1步:选服务(从最重要的开始)
# → 先做核心 API,再做内部服务
# 第2步:收集 30 天历史数据
# 用 Prometheus recording rules 预计算 SLI
# recording-rules/slo-slis.yaml
groups:
- name: slo_recording
interval: 30s
rules:
- record: sli:http_availability:ratio_rate5m
expr: |
sum(rate(http_requests_total{status!~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
- record: sli:http_latency_p99:quantile5m
expr: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
# 第3步:计算 SLO 基线
# 看 30 天 P50/P95/P99 → 设定合理目标(通常 P99 的 1.5x-2x)
# 第4步:配置告警
# 先用 warning 级别跑 2 周,调阈值,再升级到 critical
# 第5步:建立 Dashboard
# SLO 总览 + endpoint 明细 + 错误预算仪表盘
# 第6步:建立流程
# 月度 SLO 评审会 → 分析预算消耗原因 → 驱动可靠性项目
实用脚本:快速 SLO 健康检查
#!/bin/bash
# slo-health-check.sh — 快速检查所有 SLO 状态
PROM_URL="${PROMETHEUS_URL:-http://localhost:9090}"
SLO_TARGET="${SLO_TARGET:-0.999}"
echo "=== SLO Health Check ==="
echo "Target: $(echo "$SLO_TARGET * 100" | bc)%"
echo ""
# 30天可用性
availability=$(curl -s "$PROM_URL/api/v1/query" \
--data-urlencode 'query=sum(rate(http_requests_total{status!~"5.."}[30d])) / sum(rate(http_requests_total[30d]))' \
| jq -r '.data.result[0].value[1]')
echo "30d Availability: $(echo "$availability * 100" | bc -l | xargs printf '%.3f')%%"
# 错误预算剩余
budget=$(echo "1 - (1 - $availability) / (1 - $SLO_TARGET)" | bc -l)
budget_pct=$(echo "$budget * 100" | bc -l | xargs printf '%.1f')
echo "Error Budget: ${budget_pct}%"
# 判定
if (( $(echo "$budget < 0" | bc -l) )); then
echo "⚠️ CRITICAL: 错误预算已耗尽!"
elif (( $(echo "$budget < 0.2" | bc -l) )); then
echo "⚠️ WARNING: 错误预算 < 20%"
elif (( $(echo "$budget < 0.5" | bc -l) )); then
echo "🟡 CAUTION: 错误预算 < 50%"
else
echo "🟢 HEALTHY: 错误预算充足"
fi
7. 常见故障模式与 SLO 响应
| 故障场景 | SLO 表现 | 响应 |
|---|
| 单机故障 | 短暂下跌,预算轻微消耗 | 自愈/自动替换,无需人工介入 |
| 发布引入回归 | 错误率陡增,6h burn rate > 10x | 立即回滚,事后复盘 |
| 流量突发 | 延迟上涨但可用性正常 | 扩容,评估是否需要调整延迟 SLO |
| 依赖服务故障 | 可用性大幅下降 | 熔断+降级,更新 SLO(区分整体 vs 部分故障) |
| 数据库慢查询 | P99 延迟恶化 | 优化查询,评估是否需要拆分延迟 SLO |