每条监控数据都随身带着身份证:job=哪个服务、instance=哪个 worker(<机器>-<服务>-<序号>,如 253-biz-0)、label=从哪个角度看它。看懂它,图例里每条线你都能叫出名字;看不懂它,89 张图全是毛线团。
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 才唯一确定一条时间线——少看一个维度就会认错线 |
count by (instance) (siliang_process_resident_memory_bytes{job="siliang_backend_worker"}),每个 instance 应数到 1 组;再去掉 job 过滤重跑,多出来的就是 server job 的干扰线。siliang_backend_worker,biz 盘用 siliang-biz,rag/review 用 siliang-rag / siliang-review。名字里下划线、连字符是逐字区分的,抄错就查不到数据。 # 核心盘/内存盘口径:只信 per-worker 抓取 siliang_http_requests_total{job="siliang_backend_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]))
by (label) 决定图上按什么拆线。 # 钱花在哪家的实时账:按 provider 切(c12 口径) sum(rate(siliang_llm_calls_total[5m])) by (provider)
sum(...) 全抹掉只剩一条总线;sum by (handler)(...) 一个接口一条线。 # 总线 vs 分线 sum(rate(siliang_http_requests_total[5m])) # → 1 条总线 sum by (handler) (rate(siliang_http_requests_total[5m])) # → 每接口一条
# 错误示范 → 正确口径(带 job 过滤) siliang_process_resident_memory_bytes # → 可能混入 server job siliang_process_resident_memory_bytes{job="siliang_backend_worker"} # → 面板口径
# 点名:现在有几个 worker 活着(每个 instance 一组) count by (instance) (siliang_process_resident_memory_bytes{job="siliang_backend_worker"})
# b6 口径:借出数 + 上限参考线(上限 30 是"每个 worker"的) siliang_biz_db_pool_connections{state="checked_out"} + max(siliang_biz_db_pool_capacity)
instance=~"253-.*"。 # 等价于选了 HOST=253 siliang_process_resident_memory_bytes{instance=~"253-.*"}
🟣 核心盘 · 在线用户数(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)。
仪表盘顶部选 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 的问题;全部冒尖 = 全局问题
内存曲线在涨?先别猜。按身份证拆线,让凶手自己报名字。
# 每个 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 斜率恒为正 → 凶手住在这间房
怀疑自己写的查询混入了 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 张面板同口径
告警通知里最值钱的信息是 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:真累积,按下钻链排查"
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 上有慢查询拖住连接
# 错: siliang_http_requests_total # → 混入 server job 双计 # 对: siliang_http_requests_total{job="siliang_backend_worker"}
# 错: sum(rate(siliang_http_requests_total[5m])) # → 一条总线 # 对: sum by (handler)(rate(siliang_http_requests_total[5m])) # → 每接口一条
instance="253-biz-0" 复制进新查询没数据。原因:worker 序号、机器编号会随部署变化,写死就失效。正解:按前缀正则匹配,留变化余地。 # 错: {instance="253-biz-0"} # → 序号重排后失效 # 对: {instance=~"253-biz-.*"} # → 按机器+服务前缀下钻
# 错: 全量 instance 线一屏全画 # → 毛线团 # 对: topk(5, max_over_time(…[1m])) # → 只画前 5 名
# 错: 在 RSS 图里找 "/me 接口那条线" # → 维度认错 # 对: RSS 图按 instance 拆 → 线=worker;QPS 图按 handler 拆 → 线=接口
siliang_backend_worker(下划线)与 siliang-biz(连字符)是逐字匹配的,差一个字符就查不到。正解:从口径卡/面板 PromQL 里复制粘贴,不手打。 # 错: {job="siliang-backend-worker"} # → 空 results,无报错 # 对: {job="siliang_backend_worker"} # → 逐字照抄口径卡
# 错: 把 server job 数据也算进结论 # → 数字虚高且分布随机 # 对: 面板统一 job 过滤排除 server job # → 口径一致才可比
rate(sum(x)[5m]) 的曲线在 worker 重启时出现怪跳。原因:把不同身份证的 Counter 先加在一起,重启归零的断崖就说不清是谁的了。正解:先 rate 算出各自速度,再 sum 合并。 # 错: rate(sum(siliang_http_requests_total)[5m]) # → 重启断崖错乱 # 对: sum(rate(siliang_http_requests_total[5m])) # → 先 rate 再 sum
参考答案:job=哪个服务(siliang_backend_worker / siliang-biz / siliang-rag);instance=哪个 worker(<机器>-<服务>-<序号>,如 253-biz-0);label=从哪个角度看(handler/provider/outcome/generation)。三者合起来就是一条指标时间线的完整身份证。
参考答案:server job 走共享端口,请求随机打到哪个 worker 就由谁计数——同一个请求会被随机的一个 worker 记录,但多个来源混在一起时分布随机、总量虚高(同一时刻可能被多个视角重复统计)。所有面板统一用 job 过滤排除它,只信 per-worker 精确端口抓取(如 job="siliang_backend_worker")。自己写查询也要带同样的过滤。
参考答案:253 号机器(两台 4C 宿主机之一)上、backend 服务的 3 号 worker(gunicorn 4 个窗口里的最后一个)。单 IP NAT 环境下 IP 分不清谁是谁,这套语义化命名让你从图例直接定位到具体机器上的具体进程。
参考答案:① 仪表盘顶部 HOST 变量选 253(本质就是前缀下钻);② 查询里写 instance=~"253-.*";③ 在已按 instance 拆线的图里只保留 253 开头的图例。三种是同一个身份证的不同用法。