🪪 job / instance / label:指标的身份证

每条监控数据都随身带着身份证:job=哪个服务、instance=哪个 worker(<机器>-<服务>-<序号>,如 253-biz-0)、label=从哪个角度看它。看懂它,图例里每条线你都能叫出名字;看不懂它,89 张图全是毛线团。

随机命中 指标的身份证:抓取 → worker → 图上一条线,全程靠 job / instance 认人 正确路:per-worker 精确端口抓取,一人一张身份证;错误路:共享端口随机命中 → 双计(已全部排除) ✅ Prometheus per-worker 精确抓取 每 worker 独立端口抄 /metrics 无漏计 · 无重计 🚨 server job(共享端口) 随机命中任意 worker → 同一请求记两次 已从所有面板排除(口径卡声明) 253-biz-0 机器 253 · biz 服务 · 0 号 worker 253-biz-1 机器 253 · biz 服务 · 1 号 worker 253-biz-2 机器 253 · biz 服务 · 2 号 worker 253-biz-3 机器 253 · biz 服务 · 3 号 worker Grafana 图例 每条线 = 一个 worker 253-biz-0 253-biz-1 253-biz-2 253-biz-3 一条线异常 = 锁定具体进程 一条指标样本的身份证(示例口径与 biz 盘一致) siliang_http_requests_total{job="siliang-biz", instance="253-biz-0", handler="/me"} job = 哪个服务 siliang_backend_worker(backend) siliang-biz · siliang-rag · siliang-review instance = 哪个 worker <机器>-<服务>-<序号> 如 253-biz-0 · 253-backend-3 label = 切维度 handler · provider · outcome · generation by (label) 一份数据看多个角度 拆解 instance 命名:253-biz-0 253 机器编号(两台 4C 宿主机之一) biz 服务名(gunicorn 开 4 个 worker) 0 worker 序号(0~3 号窗口) − − 仪表盘顶部 HOST 变量按机器前缀下钻:选 253 = 只看这台机器上所有 worker 的线 单 IP NAT 环境里 IP 分不清谁是谁——语义化 instance 就是为此而生的

💡 一句话理解

Prometheus 里的每个指标样本都随身带一张身份证:job 写着"我属于哪个服务"(siliang_backend_worker / siliang-biz / siliang-rag),instance 写着"我是哪个 worker"(<机器>-<服务>-<序号>,如 253-biz-0 = 253 号机器上 biz 的 0 号窗口),其余 label 写着"从哪个角度看"(handler、provider、outcome、generation…)。

为什么这课重要:图上每条线就是一个身份证。会读身份证,你能把任何一条异常线定位到"某台机器的某个进程";不会读,89 张图对你来说就是一团毛线。另外有一个必须知道的口径:共享端口的 server job 会随机命中 worker 造成双计——所有面板已把它排除,你自己写查询时也要记得过滤。

🧩 费曼拆解 讲给完全没接触过的小白

把监控数据想象成快递网络。每一个包裹(指标样本)上都贴着一张运单:发件仓(job)写"北京仓/上海仓"——对应"哪个服务";详细地址(instance)写"253 号楼 biz 单元 0 室"——对应"哪台机器的哪个 worker";包裹属性(label)写"品类=生鲜、品牌=xx、签收状态=已签收"——对应"从哪个角度看这份数据"。

四两的 instance 是语义化身份证:253-biz-0 一眼读出三段信息——253 号机器(两台 4C 宿主机之一)、biz 服务、0 号 worker(gunicorn 开了 4 个窗口,这是其中第 0 个)。为什么要起这么长的名字?因为这套环境是单 IP NAT,光靠 IP 分不清谁是谁;有了语义化命名,图例里每条线都有名有姓,一条线异常就能直接定位到具体机器上的具体进程。

还有一个著名的陷阱:backend 有个共享端口的 server job——它抓谁全凭随机(请求打到哪个 worker 就算谁的),于是同一个请求会被记两次、分布却是随机的,曲线看起来像抽风。这就像一个快递员同时挂两个仓的工牌,每单被两个仓各记一次账。处理方式很干脆:所有面板统一排除 server job(只信 job="siliang_backend_worker" 这种 per-worker 精确抓取)。你看每张盘的"先读我"卡都会重申这条口径。

类比里的东西系统里对应的东西
发件仓(北京仓/上海仓)job:哪个服务——siliang_backend_worker / siliang-biz / siliang-rag / siliang-review
门牌号(253 号楼 biz 单元 0 室)instance:<机器>-<服务>-<序号>,如 253-biz-0,精确到某个 worker 进程
包裹品类/品牌/签收状态label:handler(哪个接口)/ provider(哪家供应商)/ outcome(什么结局)/ generation(GC 第几代)
挂双工牌的快递员server job 双计:共享端口随机命中 worker,同一请求记两次——已从所有面板排除
按楼栋筛运单HOST 变量:仪表盘顶部按机器前缀(如 253-)下钻,只看这台机器的线
运单上字段合起来才是完整地址指标名 + 全部 label 才唯一确定一条时间线——少看一个维度就会认错线

🛠 动手验证(3~4 步,在 Grafana 里亲手做一遍)

  1. 打开 biz 盘「HTTP QPS(按 handler)」(b1)——展开图例,每条线的名字就是 instance(253-biz-0/1/2/3),这就是"图上每条线是一个 worker"。
  2. 点掉 3 条线只留 253-biz-2——你正在看"2 号窗口"这家分店的单独流量;别家卡死也不影响它。
  3. 切到内存盘「数据库连接池使用率」(m115)——图例变成 worker × 池(sync/async/checkpointer),体会"一个 label 是一个维度"。
  4. 在 Explore 里数一数身份证——跑 count by (instance) (siliang_process_resident_memory_bytes{job="siliang_backend_worker"}),每个 instance 应数到 1 组;再去掉 job 过滤重跑,多出来的就是 server job 的干扰线。

🧠 必知必会 看懂本课全部面板的地基

job = 哪个服务
一套服务一个 job 名。四两核心/内存盘用 siliang_backend_worker,biz 盘用 siliang-biz,rag/review 用 siliang-rag / siliang-review。名字里下划线、连字符是逐字区分的,抄错就查不到数据。
# 核心盘/内存盘口径:只信 per-worker 抓取
siliang_http_requests_total{job="siliang_backend_worker"}
instance = 哪个 worker
命名规则 <机器>-<服务>-<序号>:253-biz-0 = 253 机器上 biz 的 0 号 worker;253-backend-3 = 253 机器 backend 的 3 号 worker。图例里每条线就是一个 instance。
# 按 worker 拆开看:一人一条线
max by (instance) (max_over_time(siliang_process_resident_memory_bytes[1m]))
label = 切维度
同一份数据的多个观察角度:handler(哪个接口)、provider(哪家 LLM)、outcome(成功/exception)、generation(GC 第几代)。用 by (label) 决定图上按什么拆线。
# 钱花在哪家的实时账:按 provider 切(c12 口径)
sum(rate(siliang_llm_calls_total[5m])) by (provider)
by () 决定图的颗粒度
聚合时保留哪个 label,图上就有几条线。sum(...) 全抹掉只剩一条总线;sum by (handler)(...) 一个接口一条线。
# 总线 vs 分线
sum(rate(siliang_http_requests_total[5m]))                 # → 1 条总线
sum by (handler) (rate(siliang_http_requests_total[5m]))   # → 每接口一条
server job 双计(已排除)
backend 有个共享端口的 server job,随机命中任意 worker——同一请求记两次、分布随机。所有面板已统一排除;自己写查询也必须带 job 过滤,否则数字虚高。
# 错误示范 → 正确口径(带 job 过滤)
siliang_process_resident_memory_bytes                          # → 可能混入 server job
siliang_process_resident_memory_bytes{job="siliang_backend_worker"}  # → 面板口径
per-worker 精确抓取
Prometheus 到每个 worker 的独立端口抄 /metrics,核心仪表盘 15s、其余 30s。所以 per-worker 数字无漏计无重计,可以放心做除法(错误率、池使用率)。
# 点名:现在有几个 worker 活着(每个 instance 一组)
count by (instance) (siliang_process_resident_memory_bytes{job="siliang_backend_worker"})
gunicorn 4 worker 与 per-worker 容量
backend/biz 各开 4 个 worker,每家是独立进程:独立内存、独立连接池、独立 GC。所以连接池容量是每 worker 各一份(biz DB 池 30,整机 4 个 worker 理论最多 120 条连接)。
# b6 口径:借出数 + 上限参考线(上限 30 是"每个 worker"的)
siliang_biz_db_pool_connections{state="checked_out"} + max(siliang_biz_db_pool_capacity)
HOST 变量 = 机器前缀下钻
仪表盘顶部的 HOST 变量本质是按 instance 前缀过滤:选 253 = 只看这台机器。手写查询等价于 instance=~"253-.*"。
# 等价于选了 HOST=253
siliang_process_resident_memory_bytes{instance=~"253-.*"}

🔗 在四两监控里哪里用到 理论落回你的 89 张图

🟣 核心盘 · 在线用户数(c1)↗——把所有 worker 上的 WS 会话求和:先按 job="siliang_backend_worker" 认准服务,再 sum 到一起。为什么只信这个 job?口径卡(c4)讲的就是 server job 双计排除。

📊 内存盘 · 数据库连接池使用率(m115)↗——图例是 worker × 池 两级维度(instance × pool):on(instance,pool) 把借出数和容量按同一张身份证配对相除——身份证对不齐,除法就乱套。

🟢 biz 盘 · HTTP QPS(按 handler)(b1)↗——per-worker 精确抓取、无漏计;sum by (handler) 决定图上一条线一个接口。biz 盘口径卡(b0)还写着 instance 名 = 机器-biz-序号(如 253-biz-0)。

🏭 生产实战 real world

场景 1 · "只看 253 这台机器":HOST 变量的本质

仪表盘顶部选 HOST 下钻,等价于查询里按 instance 前缀过滤。排查"某台机器有问题"时的第一动作。

# 只看 253 机器上所有 backend worker 的 RSS(m101 口径 + 前缀过滤)
max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker", instance=~"253-.*"}[1m])
# 组合下钻:某台机器 × 某个池(sync 池打满了?)
max_over_time(siliang_db_pool_connections{instance=~"253-.*", state="checked_out", pool="sync"}[1m])
# 判读:一条线冒尖 = 该机器该 worker 的问题;全部冒尖 = 全局问题

场景 2 · 找出"在爬的那条线":按 instance 定位凶手

内存曲线在涨?先别猜。按身份证拆线,让凶手自己报名字。

# 每个 worker 一条 RSS 线,不聚合(保留 instance 维度)
max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m])
# 再算每条线的斜率:谁是持续为正的那个
deriv(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1h])
# 输出里 instance=253-backend-2 斜率恒为正 → 凶手住在这间房

场景 3 · 双计自检:数一数身份证数量

怀疑自己写的查询混入了 server job?数一下时间线数量——per-worker 抓取下,每个指标每个 worker 只有一条线。

# 正确口径:4 个 worker → 每个对应 instance 数出 1
count by (instance) (siliang_process_resident_memory_bytes{job="siliang_backend_worker"})
# 去掉 job 过滤重跑:多出来的行就是 server job 的干扰
count by (instance, job) (siliang_process_resident_memory_bytes)
# 结论:写查询永远带 job 过滤,与 89 张面板同口径

场景 4 · 告警里保留身份证:别把 $labels.instance 聚合丢

告警通知里最值钱的信息是 instance——它直接告诉你去哪台机器查。表达式里不写 sum,身份证就自动保留。

groups:
  - name: siliang-per-worker
    rules:
      - alert: BackendRSSClimbPerWorker
        # 不聚合 → 告警自带 instance 标签,通知直指某台机器某个 worker
        expr: |
          deriv(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1h])
            > 5 * 1024 * 1024
        for: 15m   # 持续窗口为通用示例,线上以内存盘口径卡(m100)为准
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} RSS 每小时涨超 5MB:真累积,按下钻链排查"

场景 5 · biz 池容量是"每个 worker 一份":除法前先想身份证

biz 的 DB 池 30 是单 worker 的容量,4 个 worker 理论上限整机 120 条连接。看池面板永远记住"按 worker 拆"——这正是身份证的意义。

# b5 口径:借出 ÷ 藏书,on(instance,pool) 按同一身份证配对
max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m])
  / on (instance, pool) max_over_time(siliang_biz_db_pool_capacity[1m])
# 拆开看:哪台机器(repeat 出两张图)、哪个 worker 贴 100%
# 全体同时高 = 业务高峰;个别高 = 该 worker 上有慢查询拖住连接

⚠️ 常见坑 pitfalls

坑 1 · 自己写查询不带 job 过滤 — 症状:ad-hoc 查出来的数字比面板高一截、曲线毛糙。原因:共享端口的 server job 随机命中 worker 造成双计,面板全部排除了它,你的查询没排。正解:永远带 job 过滤,与面板同口径。
# 错: siliang_http_requests_total                        # → 混入 server job 双计
# 对: siliang_http_requests_total{job="siliang_backend_worker"}
坑 2 · sum 一把抹掉所有维度 — 症状:图上只剩一条总线,看不出谁有问题。原因:不带 by 的聚合把 handler/instance 全加没了。正解:聚合时保留你要看的维度。
# 错: sum(rate(siliang_http_requests_total[5m]))                  # → 一条总线
# 对: sum by (handler)(rate(siliang_http_requests_total[5m]))     # → 每接口一条
坑 3 · instance 写死完整单值 — 症状:instance="253-biz-0" 复制进新查询没数据。原因:worker 序号、机器编号会随部署变化,写死就失效。正解:按前缀正则匹配,留变化余地。
# 错: {instance="253-biz-0"}   # → 序号重排后失效
# 对: {instance=~"253-biz-.*"} # → 按机器+服务前缀下钻
坑 4 · 几十条 instance 线糊成毛线团 — 症状:按 instance 拆线后什么都看不清。原因:维度太细没有聚焦。正解:先 HOST 下钻到机器,再 topk 只看前 N 名。
# 错: 全量 instance 线一屏全画           # → 毛线团
# 对: topk(5, max_over_time(…[1m]))     # → 只画前 5 名
坑 5 · 以为"每条线都是一个接口" — 症状:把 RSS 图的线当接口、把 QPS 图的线当 worker。原因:每张图按什么 label 拆线是面板自己定的。正解:先看面板副标题按什么拆(QPS 按 handler、RSS 按 instance)再读线。
# 错: 在 RSS 图里找 "/me 接口那条线"       # → 维度认错
# 对: RSS 图按 instance 拆 → 线=worker;QPS 图按 handler 拆 → 线=接口
坑 6 · job 名下划线连字符混写 — 症状:查询无报错但返回空。原因:siliang_backend_worker(下划线)与 siliang-biz(连字符)是逐字匹配的,差一个字符就查不到。正解:从口径卡/面板 PromQL 里复制粘贴,不手打。
# 错: {job="siliang-backend-worker"}   # → 空 results,无报错
# 对: {job="siliang_backend_worker"}   # → 逐字照抄口径卡
坑 7 · 以为双计是采集 bug 要去修 — 症状:发现 server job 数字异常,想去改采集配置"修 bug"。原因:双计是共享端口随机命中的固有机制,不是坏账,口径上排除即可。正解:理解机制:只信 per-worker 精确抓取的 job。
# 错: 把 server job 数据也算进结论        # → 数字虚高且分布随机
# 对: 面板统一 job 过滤排除 server job    # → 口径一致才可比
坑 8 · 先聚合后 rate — 症状:rate(sum(x)[5m]) 的曲线在 worker 重启时出现怪跳。原因:把不同身份证的 Counter 先加在一起,重启归零的断崖就说不清是谁的了。正解:先 rate 算出各自速度,再 sum 合并。
# 错: rate(sum(siliang_http_requests_total)[5m])   # → 重启断崖错乱
# 对: sum(rate(siliang_http_requests_total[5m]))   # → 先 rate 再 sum

🎓 费曼自测 合上书,能讲出来才算懂

Q1 · 用一句话分别说清 job、instance、label 是什么?

参考答案:job=哪个服务(siliang_backend_worker / siliang-biz / siliang-rag);instance=哪个 worker(<机器>-<服务>-<序号>,如 253-biz-0);label=从哪个角度看(handler/provider/outcome/generation)。三者合起来就是一条指标时间线的完整身份证。

Q2 · server job 为什么会双计?面板是怎么处理的?

参考答案:server job 走共享端口,请求随机打到哪个 worker 就由谁计数——同一个请求会被随机的一个 worker 记录,但多个来源混在一起时分布随机、总量虚高(同一时刻可能被多个视角重复统计)。所有面板统一用 job 过滤排除它,只信 per-worker 精确端口抓取(如 job="siliang_backend_worker")。自己写查询也要带同样的过滤。

Q3 · instance 叫 253-backend-3,读出什么信息?

参考答案:253 号机器(两台 4C 宿主机之一)上、backend 服务的 3 号 worker(gunicorn 4 个窗口里的最后一个)。单 IP NAT 环境下 IP 分不清谁是谁,这套语义化命名让你从图例直接定位到具体机器上的具体进程。

Q4 · 只想看 253 这台机器的数据,有哪几种办法?

参考答案:① 仪表盘顶部 HOST 变量选 253(本质就是前缀下钻);② 查询里写 instance=~"253-.*";③ 在已按 instance 拆线的图里只保留 253 开头的图例。三种是同一个身份证的不同用法。

← 上一课:泄漏判定心法:锯齿 / 爬升 / 平台期 📚 课程目录 下一课:5xx vs 4xx:谁的错 →