LabHub
学习 学习路径 课程

可观测性

不能把 user_id 放进标签的原因

在 LabHub 中继续学习

一句话总结

指标的时间序列数量等于各标签值组合数量的乘积。一个标签选择错误,就可能产生数百万条时间序列;从那一刻起,监控系统会比被观测对象更早崩溃。

概念图: 时间序列数量 · 乘积 · 1,600 条 · 1.6 亿条

为什么需要它

我们为 http_requests_total 添加标签。

http_requests_total{method, status, endpoint}

时间序列数量为 5 × 8 × 40 = 1,600 条,尚可接受。

接着有人提出“希望按用户查看”,于是增加 user_id。如果有 10 万名用户,

5 × 8 × 40 × 100,000 = 1.6 亿条

Prometheus 会为每条时间序列在内存中维护索引。服务器会因 OOM 崩溃,于是用于观测事故的手段,会因为事故本身而消失。

引发基数爆炸的标签

以下内容绝不能放进标签。

标签 危险原因
user_id, session_id 随用户数量增长,没有上限
request_id, trace_id 每个请求都唯一,无限增长
email, ip 实际上无限增长,同时包含个人信息
timestamp 每个时刻产生新时间序列(把时间放进时间序列的矛盾)
url(包含查询字符串) ?page=1, ?page=2……无限增长
完整错误消息 消息中混入具体值时无限增长

最后一项尤其危险。如果把 error="connection to 10.0.3.17:5432 timed out" 这样包含 IP 和端口的消息作为标签,组合数量会爆炸。应使用 error="db_timeout" 这类经过分类的值

判断标准——这个标签可能有多少种值

添加标签前,应先问自己以下问题。

  1. **可能值有多少种?**必须能够数出并给出答案。“很多”不是答案。
  2. **数量会随时间增长吗?**如果会,是否存在上限?
  3. **真的会根据该标签创建告警吗?**如果只是可能在监控面板上看一次,就应该放到日志或追踪中,而不是指标中。

经验目标是每个标签的值不超过 100 种,每个指标的时间序列不超过 1 万条

URL 必须进行规范化

# 나쁨 — 주문 개수만큼 시계열
endpoint="/api/orders/8f3a91"

# 좋음 — 라우트 패턴
endpoint="/api/orders/:id"

使用框架的路由定义时通常会自动规范化。手工拼接字符串则一定会导致爆炸。这不是会不会失误的问题,而只是何时发生的问题。

分别使用三种信号

有时确实需要高基数信息,例如“为什么这个用户的请求很慢”。但这不属于指标的职责。

信号 基数要求 回答的问题
指标 必须较低 “数量有多少、速度有多快?”用于趋势和告警
日志 可以较高 “当时发生了什么?”用于单个事件
追踪 可以较高 “时间花在哪里?”用于单次请求路径

先通过指标收到告警 → 确定时间范围 → 再用日志和追踪深入查看单个请求。三种信号不是替代关系,而是调查顺序

已经发生爆炸时

生产现场中的表现

爆炸发生后的回退顺序

基数问题通常总是在事故发生后才被发现。Prometheus 可能耗尽内存并崩溃,或者查询超时无法返回。此时需要遵循固定顺序。

**首先统计什么占用了多少。**Prometheus 状态页面(/tsdb-status)会显示产生最多时间序列的指标和标签,也可以通过命令查看。

topk(10, count by (__name__)({__name__=~".+"}))
count(app_request_duration_seconds_bucket)
count(count by (user_id)(app_request_total))

最后一行表示该标签实际拥有多少种值。如果数字达到数千,仅这一个标签就足以成为原因。

**应在写入存储之前进行阻断。**修复并重新部署应用才是正确答案,但这需要时间。在此期间,应在抓取配置中删除标签,或直接丢弃整个指标。

metric_relabel_configs:
  - source_labels: [__name__]
    regex: 'app_request_total'
    target_label: user_id
    replacement: ''            # 라벨 값을 비운다
  - source_labels: [__name__]
    regex: 'debug_.*'
    action: drop               # 이 지표는 아예 저장하지 않는다

**已经存储的时间序列不会自行消失。**即使不再写入,它们仍会在整个保留期内占用内存。情况紧急时,可以通过管理 API 删除(必须启用 --web.enable-admin-api,而且删除后无法恢复)。

**直方图会悄无声息地成倍增加。**每个桶就是一条时间序列。一个包含 12 个桶的直方图再乘以三个标签,很快就会达到数万条。如果无法减少标签,应先减少桶数量。

**避免重犯错误,需要部署前检查。**从代码中提取指标名称和标签列表,只要出现不在允许清单中的标签,就在 CI 阶段阻止部署。依靠人的注意力,问题一定会在繁忙时期再次出现。

下一项检查的内容

后续测验将根据标签值的乘积计算时间序列数量,并区分指标、日志和追踪的职责。面对已经开始爆炸的场景,还要判断应先采取采集阶段缓解,还是先完成应用程序的根本修复。