4. 基于 Prometheus 的 SLI 采集:使用 Histogram 和 Summary 记录请求延迟,使用 Counter 记录错误计数,指标命名规范与标签设计
SLI(Service Level Indicator)是 SLO 的量化基石。Prometheus 通过四种核心指标类型(Counter、Gauge、Histogram、Summary)来采集 SLI 数据。本章聚焦于 请求延迟 和 错误计数 两个最关键的 SLI,讲解如何正确使用 Histogram/Summary 记录延迟分布,使用 Counter 记录错误次数,并遵循 Prometheus 官方命名规范与标签设计原则。
4.1 核心 SLI 指标类型选择
| SLI 维度 | 推荐指标类型 | 核心用途 | 典型指标名 |
|---|---|---|---|
| 请求延迟 | Histogram(首选)或 Summary | 记录延迟分布,计算 p50/p90/p99 分位数 | http_request_duration_seconds |
| 错误计数 | Counter | 累计错误请求次数,计算错误率 | http_requests_total(含 status 标签) |
| 请求总量 | Counter | 累计总请求次数,作为错误率分母 | http_requests_total |
最佳实践: 对于延迟 SLI,优先使用 Histogram 而非 Summary。Histogram 允许在 PromQL 层面聚合多个实例的分位数,而 Summary 的分位数只能在客户端计算,无法跨实例聚合。
4.2 使用 Histogram 记录请求延迟
Histogram 将观测值放入预定义的桶(bucket)中,同时记录总计数和总和。以下是一个典型的 HTTP 请求延迟 Histogram 采集示例:
// 定义 Histogram 指标
http_request_duration_seconds = Histogram(
'http_request_duration_seconds',
'HTTP request latency in seconds',
['method', 'path', 'status'],
buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)
)
// 在请求处理结束时记录延迟
def handle_request(method, path):
start = time.time()
try:
# 处理请求逻辑
response = process_request(method, path)
status = response.status_code
except Exception as e:
status = 500
finally:
duration = time.time() - start
http_request_duration_seconds.labels(
method=method, path=path, status=status
).observe(duration)
桶(Bucket)设计原则:
- 桶的范围应覆盖预期的延迟分布,通常从 5ms 到 10s。
- 在 SLO 目标值附近(如 200ms、500ms)增加桶密度,提高分位数精度。
- Prometheus 自动添加
+Inf桶,确保所有观测值都被计数。
4.3 使用 Summary 记录请求延迟(备选方案)
当无法在服务端聚合分位数,或需要精确的滑动窗口分位数时,可以使用 Summary。但注意 Summary 的分位数是客户端计算的,无法跨进程聚合。
// 定义 Summary 指标
http_request_duration_summary = Summary(
'http_request_duration_summary',
'HTTP request latency summary in seconds',
['method', 'path', 'status'],
quantiles=(0.5, 0.9, 0.99)
)
// 记录延迟
http_request_duration_summary.labels(
method='GET', path='/api/users', status='200'
).observe(0.123)
注意: Summary 的
quantiles 参数是固定的,无法在查询时动态调整。如果未来需要新的分位数(如 p95),必须修改代码并重启服务。Histogram 则无此限制。
4.4 使用 Counter 记录错误计数
错误计数通常作为 http_requests_total 的一个标签维度,而不是单独定义一个指标。这样可以通过 PromQL 的 rate() 和 sum() 灵活计算错误率。
// 定义 Counter 指标
http_requests_total = Counter(
'http_requests_total',
'Total number of HTTP requests',
['method', 'path', 'status']
)
// 在请求结束时递增计数
def handle_request(method, path):
status = process_request(method, path)
http_requests_total.labels(
method=method, path=path, status=status
).inc()
错误率计算 PromQL 示例:
# 5 分钟内错误率(status 以 5xx 开头)
rate(http_requests_total{status=~"5.."}[5m])
/
rate(http_requests_total[5m])
4.5 指标命名规范与标签设计
Prometheus 官方命名规范要求指标名遵循 namespace_subsystem_unit_suffix 格式。标签设计应遵循“少而精”原则,避免基数爆炸。
| 规范维度 | 规则 | 正确示例 | 错误示例 |
|---|---|---|---|
| 指标名基础格式 | namespace_subsystem_unit_suffix |
http_request_duration_seconds |
httpLatency |
| 单位后缀 | 使用 秒 作为时间单位,明确标注 _seconds |
_seconds, _bytes, _total |
_ms, _milliseconds |
| Counter 后缀 | Counter 类型指标必须以 _total 结尾 |
http_requests_total |
http_requests |
| 标签基数控制 | 标签值组合数 < 10000,避免高基数标签如 user_id |
method, path, status |
user_id, session_id, ip |
| 标签命名 | 小写字母 + 下划线,语义清晰 | http_method, status_code |
HTTPMethod, sc |
标签设计黄金法则: 每个标签的值集合应该是 有限且可枚举 的。例如
method 只有 GET/POST/PUT/DELETE 等少数值;path 应归一化为路由模板(如 /api/users/:id),而不是原始请求路径。
4.6 SLI 采集架构图
下图展示了基于 Prometheus 的 SLI 采集整体架构,从应用埋点到指标存储与查询:
4.7 完整采集示例:结合 Histogram 与 Counter
以下是一个完整的 Python Flask 应用示例,同时采集请求延迟 Histogram 和请求计数 Counter:
from prometheus_client import Histogram, Counter, generate_latest
from flask import Flask, request, Response
import time
app = Flask(__name__)
# 定义指标
REQUEST_DURATION = Histogram(
'http_request_duration_seconds',
'HTTP request latency in seconds',
['method', 'endpoint', 'status'],
buckets=(0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0)
)
REQUEST_COUNT = Counter(
'http_requests_total',
'Total number of HTTP requests',
['method', 'endpoint', 'status']
)
@app.before_request
def before_request():
request.start_time = time.time()
@app.after_request
def after_request(response):
duration = time.time() - request.start_time
REQUEST_DURATION.labels(
method=request.method,
endpoint=request.path,
status=response.status_code
).observe(duration)
REQUEST_COUNT.labels(
method=request.method,
endpoint=request.path,
status=response.status_code
).inc()
return response
@app.route('/metrics')
def metrics():
return Response(generate_latest(), mimetype='text/plain')
@app.route('/api/users')
def get_users():
return {'users': ['alice', 'bob']}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
4.8 标签设计实战:避免高基数陷阱
标签设计不当会导致 Prometheus 存储爆炸和查询性能下降。以下对比展示了正确与错误的标签设计:
| 场景 | 错误标签设计 | 正确标签设计 | 原因 |
|---|---|---|---|
| HTTP 路径 | path="/api/users/123" |
endpoint="/api/users/:id" |
原始路径包含用户 ID,基数无限 |
| HTTP 方法 | method="GET"(正确) |
method="GET" |
方法本身基数低,无需修改 |
| HTTP 状态码 | status="200"(正确) |
status="200" |
状态码有限,直接使用即可 |
| 用户标识 | user_id="alice" |
不使用 user_id 标签 | 用户 ID 基数随用户增长,导致高基数 |
| 请求来源 IP | client_ip="10.0.0.1" |
不使用 client_ip 标签 | IP 地址基数高,且变化频繁 |
标签基数检查: 在生产环境中,建议使用
promtool tsdb analyze 命令分析 TSDB 的标签基数。如果某个标签的取值数量超过 10000,应立即优化。
4.9 Histogram 与 Summary 对比总结
| 对比维度 | Histogram | Summary |
|---|---|---|
| 分位数计算位置 | 服务端(PromQL 计算) | 客户端(应用内计算) |
| 跨实例聚合 | 支持(通过 histogram_quantile) |
不支持(只能查看单个实例) |
| 分位数灵活性 | 查询时动态指定(如 p50/p90/p99) | 客户端固定,无法动态调整 |
| 存储开销 | 较高(每个桶一个时间序列) | 较低(仅存储分位数、总和、计数) |
| 适用场景 | 需要聚合多个实例的分位数,或需要灵活查询 | 无法在服务端聚合,或需要精确滑动窗口分位数 |
推荐: 在大多数 SLO 场景中,使用 Histogram 采集延迟 SLI。它提供了更好的灵活性和聚合能力,是 Prometheus 官方推荐的方式。