3. Prometheus SLI 采集:核心指标采集实战

SLI(Service Level Indicator)是 SLO 的量化基础。本章聚焦于如何使用 Prometheus 采集请求总数、成功数、延迟分布等核心 SLI 指标,确保数据源准确、完整、可审计。采集质量直接决定误差预算台账的可靠性。

3.1 SLI 指标分类与采集策略

根据 Google SRE 经典模型,SLI 通常分为三类:吞吐量(请求总数)、成功率(成功请求占比)、延迟(响应时间分布)。每类指标在 Prometheus 中的采集方式不同,需根据应用协议(HTTP/gRPC/自定义)选择对应 Exporter 或埋点方案。

SLI 类别 核心指标 Prometheus 指标类型 典型采集方式
吞吐量 请求总数 Counter HTTP 请求计数器(http_requests_total)
成功率 成功请求数 / 错误请求数 Counter 按状态码分类的计数器(http_requests_total{status="2xx"})
延迟 P50 / P90 / P99 延迟 Histogram / Summary 请求耗时直方图(http_request_duration_seconds)
采集原则:所有 SLI 指标必须携带足够的标签维度(如 service、endpoint、method),以便后续按 SLO 边界进行聚合和筛选。标签基数需控制在合理范围内,避免 Prometheus 存储爆炸。

3.2 请求总数与成功数采集:Counter 模式

请求总数和成功数通常使用 Counter 类型指标。Counter 只增不减,适合累计计数场景。以下是一个基于 Prometheus 客户端库(Go 示例)的埋点代码片段:

// 定义计数器
var (
    httpRequestsTotal = prometheus.NewCounterVec(
        prometheus.CounterOpts{
            Name: "http_requests_total",
            Help: "Total number of HTTP requests.",
        },
        []string{"service", "endpoint", "method", "status_class"},
    )
)

// 在请求处理中间件中记录
func MetricsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 请求进入时增加总数
        httpRequestsTotal.With(prometheus.Labels{
            "service":      "user-service",
            "endpoint":     r.URL.Path,
            "method":       r.Method,
            "status_class": "total",
        }).Inc()

        // 包装 ResponseWriter 以捕获状态码
        wrapped := &responseWriter{ResponseWriter: w, statusCode: 200}
        next.ServeHTTP(wrapped, r)

        // 根据状态码分类增加成功/失败计数
        statusClass := classifyStatus(wrapped.statusCode)
        httpRequestsTotal.With(prometheus.Labels{
            "service":      "user-service",
            "endpoint":     r.URL.Path,
            "method":       r.Method,
            "status_class": statusClass,
        }).Inc()
    })
}

func classifyStatus(code int) string {
    if code < 500 {
        return "success" // 2xx, 3xx, 4xx 视为成功(根据 SLO 定义调整)
    }
    return "error"
}
关键点:成功/失败的分类逻辑必须与 SLO 定义一致。例如,某些服务将 4xx 视为成功(客户端错误),而将 5xx 视为失败。务必在采集层明确分类规则,并在 Prometheus 标签中体现。

3.3 延迟采集:Histogram 与 Summary 选择

延迟是 SLI 中最复杂的指标。Prometheus 提供两种方式:Histogram(直方图)和 Summary(摘要)。两者都能计算分位数,但适用场景不同。

特性 Histogram Summary
分位数计算位置 查询端(PromQL 计算) 客户端(Exporter 预计算)
聚合能力 支持跨实例聚合(histogram_quantile) 不支持聚合
存储开销 较高(需定义 bucket 边界) 较低(只输出预计算分位数)
推荐场景 需要全局 P99 延迟、多维度聚合 单实例、无需聚合的延迟监控

对于 SLO 误差预算计算,推荐使用 Histogram,因为需要跨多个实例聚合全局延迟分布。以下是 Histogram 埋点示例:

// 定义直方图,bucket 边界覆盖目标延迟范围
var (
    httpRequestDuration = prometheus.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "http_request_duration_seconds",
            Help:    "HTTP request latency in seconds.",
            Buckets: prometheus.DefBuckets, // 默认: .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10
        },
        []string{"service", "endpoint"},
    )
)

// 在请求处理中间件中记录耗时
func LatencyMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        duration := time.Since(start).Seconds()
        httpRequestDuration.With(prometheus.Labels{
            "service":  "user-service",
            "endpoint": r.URL.Path,
        }).Observe(duration)
    })
}
Bucket 边界设计:Bucket 必须覆盖 SLO 目标延迟。例如,若 SLO 要求 99% 请求在 500ms 内完成,则 bucket 应包含 0.5 边界,并在 0.5 附近有足够密度。建议使用 exponentialBuckets(0.001, 2, 15) 生成指数分布 bucket。

3.4 SLI 采集架构与数据流

下图展示了从应用埋点到 Prometheus 存储的完整 SLI 数据流,包含多服务、多实例的采集拓扑:

SLI 采集架构与数据流 SLI 采集架构与数据流 Service A 实例 1 / 实例 2 Service B 实例 1 / 实例 2 Service C 实例 1 / 实例 2 /metrics 端点暴露 Prometheus Server 拉取 /metrics 每 15s 一次 TSDB 存储 SLI 时间序列 保留 30 天 PromQL 查询 SLI 聚合计算 标签:service, endpoint status_class, method

3.5 多协议 SLI 采集方案对比

不同协议的服务需要不同的采集方案。以下表格总结了常见协议的 SLI 采集方式:

协议 推荐 Exporter / 库 核心指标 注意事项
HTTP (REST) Prometheus 客户端库 / Nginx Exporter http_requests_total, http_request_duration_seconds 注意 4xx/5xx 分类逻辑
gRPC gRPC Prometheus 拦截器 grpc_server_handling_seconds, grpc_server_started_total gRPC 状态码映射到 success/error
自定义 TCP 自定义 Exporter / Textfile Collector 业务自定义 Counter / Gauge 需自行实现指标暴露端点
消息队列 Kafka Exporter / RabbitMQ Exporter kafka_consumer_lag, rabbitmq_queue_messages 延迟指标需从消费端采集

3.6 采集质量验证与常见陷阱

SLI 采集完成后,必须进行质量验证,避免“垃圾进垃圾出”。以下是验证清单和常见陷阱:

  • 指标命名一致性:所有服务使用统一的指标命名规范(如 http_requests_total),避免大小写、下划线不一致。
  • 标签基数控制:避免将用户 ID、请求 ID 等高基数标签加入 SLI 指标,会导致 Prometheus 内存飙升。
  • Counter 重置检测:使用 rate() 函数时,确保 Counter 在服务重启后正确重置,避免计算错误。
  • Histogram 桶溢出:如果请求延迟超过最大 bucket 边界,会落入 +Inf 桶,导致分位数计算偏差。需定期检查 +Inf 桶计数。
  • 采集延迟:Prometheus 拉取间隔(默认 15s)与 SLO 评估窗口(如 30 天)相比可忽略,但高精度延迟分析需考虑 scrape 抖动。
验证命令:在 Prometheus 中执行以下查询,检查指标是否正常采集:
count by (service) (http_requests_total) — 查看各服务是否有数据
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) — 验证 P99 延迟计算

3.7 本章小结