Metrics 与 Prometheus
Prometheus数据模型、四类指标、标签基数、PromQL、Histogram、RED与USE、SLO及Agent指标设计。
目录 · 23 节
Metrics 与 Prometheus
Metrics将大量运行事件聚合为时间序列,以较低成本支持趋势分析和告警,但不保留单次请求的完整上下文。因此,Metrics适合衡量异常范围和变化趋势,不适合单独还原具体请求的执行过程。
1. Prometheus的数据模型
一条时间序列由指标名和完整标签集合唯一确定:
http_server_requests_total{
service="checkout-api",
method="POST",
route="/orders",
status="500"
}
标签的任意值发生变化,都是一条新时间序列。假设一个指标包含三个标签:
service:20种
route:80种
status:6种
理论组合上限为:
如果再加入100万种user_id,时间序列数量将显著增长。因此service、route、method、status适合作为有界标签;trace_id、request_id、完整URL、用户ID和错误消息不适合。
高基数字段应写入日志、Trace属性或Exemplar,不应存储为Prometheus标签。
2. 四类指标的选择
| 类型 | 语义 | 典型例子 | 常用查询 |
|---|---|---|---|
| Counter | 单调增加,进程重启可归零 | 请求数、错误数、Token总量 | rate、increase |
| Gauge | 可增可减的瞬时值 | 并发数、队列深度、内存 | avg_over_time、max_over_time |
| Histogram | 把观测值累计进Bucket | 延迟、响应大小、Tool耗时 | histogram_quantile |
| Summary | 客户端计算滑动窗口Quantile | 单实例延迟分位数 | 直接读取quantile序列 |
两个常见错误:
- 用Gauge记录请求总数,使累计值可能随进程状态发生下降;
- 用Counter记录当前队列长度,导致该指标无法表达队列出队后的下降过程。
Histogram与Summary
Histogram输出_bucket、_sum和_count,可以跨实例聚合;Summary的Quantile通常在客户端计算,不能把不同实例的P99直接取平均。
如果服务需要按集群聚合P95/P99,通常优先使用Histogram。Bucket需要根据SLO边界和实际分布设计;直接沿用默认Bucket可能无法提供所需精度。
3. Prometheus如何采集
典型模型是Pull:应用暴露/metrics,Prometheus按Scrape Interval定期抓取。Service Discovery负责发现Target,Relabel负责修改或过滤标签,TSDB保存样本,Recording Rule预计算昂贵查询,Alerting Rule把结果交给告警系统。
Application / Exporter
→ /metrics
→ Prometheus Scrape
→ TSDB
→ PromQL / Recording Rule / Alert Rule
→ Grafana / Alertmanager
短生命周期批处理无法稳定等待Scrape,可以通过更合适的作业设计或受控Push入口处理;不能因此让所有在线服务都推送,否则Target健康和生命周期语义会变得更难管理。
4. 常用PromQL计算
QPS
sum by (service) (
rate(http_server_requests_total[5m])
)
rate估计Counter在指定区间内的平均每秒速率,并能处理Counter Reset。直接计算“当前值减去五分钟前的值”会在进程重启时产生错误结果。
错误率
sum(rate(http_server_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_server_requests_total[5m]))
数学上是:
分子和分母必须保持相同的聚合维度。若分子按service聚合、分母按全局聚合,查询虽然可以执行,但结果不具备预期业务语义。
平均延迟
sum(rate(http_request_duration_seconds_sum[5m]))
/
sum(rate(http_request_duration_seconds_count[5m]))
P99
Classic Histogram:
histogram_quantile(
0.99,
sum by (service, le) (
rate(http_request_duration_seconds_bucket[5m])
)
)
histogram_quantile在Bucket内插值估计分位数,因此精度受Bucket边界影响。P99是分布尾部,不等于最大值,也不能从平均值推导出来。
5. RED、USE与业务指标
面向请求的服务先看RED:
Rate:请求速率
Errors:错误比例
Duration:延迟分布
面向资源看USE:
Utilization:资源忙碌比例
Saturation:排队或耗尽程度
Errors:资源错误
例如数据库连接池:当前使用率是Utilization,等待连接的请求数是Saturation,获取连接超时是Errors。
还要补业务指标。HTTP 200不表示订单创建成功,模型返回文本也不表示Agent完成任务。技术指标、业务结果与用户体验必须同时存在。
6. Agent指标设计
一条Agent任务可以拆成:
| 层级 | 指标示例 |
|---|---|
| Task | agent_tasks_total{status,reason}、任务耗时 |
| Planner | Step数量、Replan、Loop、停止原因 |
| Model | TTFT、输入输出Token、缓存命中、Rate Limit |
| Tool | 调用次数、P95、错误、重试、拒绝、审批 |
| Retrieval | 候选数、耗时、空结果、索引版本 |
| Answer | Task Success、人工接管、引用解析失败 |
不应将run_id、prompt或tool_arguments写入标签。推荐使用以下分层方式:
Metrics:agent_tool_calls_total{tool="loki",status="timeout"}
Trace:run_id、tool参数摘要、Span状态
Logs:脱敏后的错误上下文
Go埋点示例
toolCalls.WithLabelValues(toolName, status).Inc()
toolLatency.WithLabelValues(toolName).Observe(duration.Seconds())
activeTasks.Inc()
defer activeTasks.Dec()
toolName必须来自受控注册表;如果允许模型动态生成Tool名称,标签基数将不可预测。
7. SLI、SLO与Error Budget
SLI是测量方式,SLO是目标,Error Budget是允许失败的额度。
若30天内共有100万次任务,SLO要求成功率不低于99.9%,允许失败:
Agent的SLI不能只用HTTP成功率,可定义:
Good Event = 在Deadline内完成任务
AND 没有危险动作
AND 关键证据齐全
AND 输出Schema合法
Burn Rate
Burn Rate表示错误预算消耗速度:
99.9% SLO的允许错误率是0.1%。若当前错误率为1%,Burn Rate为10,意味着按当前速度消耗预算的速度是正常额度的十倍。
8. 告警设计原则
优先对用户症状告警,而不是对每一种可能原因都打电话:
- 高层告警:成功率、错误率、P99、队列等待;
- Dashboard与Runbook:连接池、CPU、GC、下游依赖用于定位原因;
- 低层原因只有在需要独立行动时才Page。
告警规则至少包含:
表达式 + 持续时间 + 严重级别 + Owner
+ 影响说明 + Dashboard + Runbook + 恢复条件
使用for过滤瞬时尖峰,配置恢复阈值或持续恢复窗口减少Flapping。无法对应明确处置动作的通知不应配置为告警。
9. Recording Rule与Dashboard
复杂、频繁执行的PromQL应变成Recording Rule:
- record: service:http_error_ratio:rate5m
expr: |
sum by (service) (rate(http_server_requests_total{status=~"5.."}[5m]))
/
sum by (service) (rate(http_server_requests_total[5m]))
好处是查询快、定义统一,Dashboard和告警不会各写一份近似但不相同的公式。规则本身要做单元测试,尤其检查空分母、标签丢失和聚合维度。
10. 监控系统的自监控
至少检查:
- Target是否
up; - Scrape是否超时或样本被拒绝;
- Rule Evaluation是否失败或过慢;
- TSDB磁盘、WAL、Compaction和Retention;
- Remote Write队列、失败和积压;
- 告警从触发到通知的整条路径。
如果唯一的Prometheus实例不可用,且告警规则仅存在于该实例中,团队将无法收到相应故障通知。因此需要监控可观测性系统自身,并根据可靠性目标设计冗余。
11. 高频追问
为什么不能用平均延迟代替P99?
平均值会掩盖长尾。若九十九个请求耗时10ms、一个请求耗时10s,平均值约为110ms,但最慢请求仍对单个用户造成显著影响。
Histogram为什么能跨实例聚合?
各实例报告相同Bucket边界下的累计计数,可以先按le求和再估计Quantile。Summary的客户端Quantile通常不可直接合并。
为什么trace_id不能做Label?
它几乎每次请求都不同,会为每次请求创建新时间序列。应用Exemplar或Trace/Log字段保存关联信息。
Grafana和Prometheus是什么关系?
Prometheus负责采集、存储和查询指标;Grafana将Prometheus配置为Data Source,负责展示、探索和告警编排。删除Dashboard不会删除Prometheus中的指标;Prometheus不可用时,Dashboard也无法获得相应数据。