ARCHIVE / INITIALIZING000%
正在载入档案界面SYS.07
Interview Prep

Metrics 与 Prometheus

Prometheus数据模型、四类指标、标签基数、PromQL、Histogram、RED与USE、SLO及Agent指标设计。

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种

理论组合上限为:

Nseries=20×80×6=9600N_{series}=20\times80\times6=9600

如果再加入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]))

数学上是:

ErrorRate=5xx requests/sall requests/s\mathrm{ErrorRate} =\frac{\mathrm{5xx\ requests/s}}{\mathrm{all\ requests/s}}

分子和分母必须保持相同的聚合维度。若分子按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任务可以拆成:

层级指标示例
Taskagent_tasks_total{status,reason}、任务耗时
PlannerStep数量、Replan、Loop、停止原因
ModelTTFT、输入输出Token、缓存命中、Rate Limit
Tool调用次数、P95、错误、重试、拒绝、审批
Retrieval候选数、耗时、空结果、索引版本
AnswerTask 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%,允许失败:

1,000,000×(1−0.999)=10001{,}000{,}000\times(1-0.999)=1000

Agent的SLI不能只用HTTP成功率,可定义:

Good Event = 在Deadline内完成任务
             AND 没有危险动作
             AND 关键证据齐全
             AND 输出Schema合法
SLI=Good EventsEligible Events\mathrm{SLI}=\frac{\mathrm{Good\ Events}}{\mathrm{Eligible\ Events}}

Burn Rate

Burn Rate表示错误预算消耗速度:

BurnRate=ObservedErrorRate1−SLO\mathrm{BurnRate} =\frac{\mathrm{ObservedErrorRate}}{1-\mathrm{SLO}}

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也无法获得相应数据。

参考资料