你在核心盘学过的每个体检项目,biz 这张盘原样再量一遍——看懂了核心盘,这 11 张图全部秒懂。覆盖 biz 盘 11 张面板(b0–b10)· 业务后端仪表盘 siliang-biz(job=siliang-biz · 刷新 30s)。
biz 是「另一家连锁分店」:独立于 backend 的 gunicorn 多 worker 业务后端(APP_WORKERS 默认 4)。这张盘不发明任何新指标——就是把核心盘已经教过的体检项目(HTTP 四件套、连接池两对卡、RSS/GC)在 biz 身上原样再量一遍,项目相同、读法相同,只有「读数」因服务而异。
所以学这课只需要记一张映射表:b1↔c16、b2↔c17、b3↔c18、b4↔c19、b5/b6↔m115/m116、b7/b8↔m117/m118、b9↔m101、b10↔m126;再加 biz 特有的三件事:handler 不含 /api/v2 前缀、Redis 与 Agent 服务共享(对方挤占)、池面板按机器 repeat 成对出现。
连锁体检的故事。你已经在前面的课程里学会了整套体检流程:量血压(HTTP 四件套:QPS、5xx、并发、延迟)、量血液粘度(连接池:借出 ÷ 藏书)、量体温(RSS 形态)、看白细胞(GC 速率)。现在公司新开了一家分店——biz 服务,设备与流程和总店(backend)完全一样,只是换了店名。体检报告自然也长一个样:项目一样、正常范围的判读方法一样,你不需要重新学,只需要对号入座。
这家分店的门牌号。biz 用 gunicorn 开了 4 家门店(APP_WORKERS 默认 4,每家是独立进程、独立内存、独立连接池)。Prometheus 每 30 秒来抄一次表,每家门店单独一本账:指标身份证上的 instance 写着「机器-biz-序号」,比如 253-biz-0 就是 253 这台机器上 biz 的 0 号 worker。菜单上的菜名(handler)是子路由模板,比如 /me——不含 /api/v2 前缀,去日志里搜接口名时别把前缀带上。
一处特别的合租。biz 的 Redis 图书馆不是独享的:它与 Agent 服务共享同一个 Redis(连接上限 max_connections=50)。合租意味着室友用得多,也会挤占你的池子——所以 b7/b8 的使用率莫名走高时,除了查 biz 自己,还要想想室友干了什么。另外这张盘的池面板按机器 repeat 成对出现(每台机器一对图),下钻时用仪表盘顶部的 HOST 变量按机器前缀切换。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 连锁店同一套体检流程 | biz 盘与核心盘同构的 11 张图(映射表 b1↔c16 … b10↔m126),读法完全复用。 |
| 分店店名 | job="siliang-biz"——biz 面板的指标全挂在这个 job 下,手工查询记得带上。 |
| 门店编号(253-biz-0) | instance = 机器-biz-序号。图例里每条线就是一个 worker,一条线异常即可定位到具体 worker。 |
| 菜单短名(/me) | handler 子路由模板,不含 /api/v2 前缀——图例里的接口名比完整路径短。 |
| 每家门店自己的藏书柜 | per-worker DB 池:10 + overflow 20 = 30;单机 4 worker 合计至多 120 条连接。 |
| 与邻居合租的图书馆 | Redis 池与 Agent 服务共享,上限 max_connections=50——室友挤占也会推高你的使用率。 |
siliang-biz、刷新 30s;对比核心盘的 15s,biz 的节奏整体慢半拍,属正常配置差异。APP_WORKERS 默认 4),每个是独立进程:独立内存、独立连接池、独立 GC。所以每张图都是 per-worker 视角,一条线异常就能定位到具体分店。 # instance 命名:<机器>-biz-<序号> siliang_biz_http_requests_total{instance="253-biz-0"} # → 单独看 0 号 worker 的请求计数
job="siliang-biz" 下。biz 是 per-worker 精确抓取、无漏计(口径卡 b0);自己写查询时带上 job 过滤,避免混入其他服务的系列。 # 手工查询的标准姿势: sum(rate(siliang_biz_http_requests_total{job="siliang-biz"}[5m]))
/me),不含 /api/v2 前缀。按 handler 去日志里搜的时候,别把前缀带上,否则一行都搜不到。 # 图例显示 /me ↔ 完整路径是 /api/v2/me
sum(rate(siliang_biz_http_requests_total[5m])) by (handler)# 使用率 = 借出 ÷ 藏书(b5 的口径) max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m]) / max_over_time(siliang_biz_db_pool_capacity[1m])
max_connections=50,且这个 Redis 与 Agent 服务共享——对方用得多也会挤占你的额度。b7 使用率走高,先想室友再想自己。 # Redis 借出数(b8 绝对值口径,上限 50 参考线) siliang_biz_redis_pool_connections{state="in_use"}
# 按机器前缀下钻(HOST 变量的底层逻辑) sum by (instance) (siliang_biz_db_pool_connections{state="checked_out"})
# 每小时涨多少字节(口径卡告警:deriv > 5MB/h)
deriv(siliang_biz_process_resident_memory_bytes[1h])# 垃圾车出车频率(b10 口径,按代拆)
sum by (generation) (rate(siliang_biz_python_gc_collections_total[5m]))它是什么:本盘使用说明书。三件事必须先背下来:生产是 gunicorn 4 worker(APP_WORKERS 默认 4);instance 名 = 机器-biz-序号(如 253-biz-0);handler 是子路由模板(如 /me,不含 /api/v2)。
回答什么问题:这张盘的「身份证规则」是什么?手工查询、日志检索、告警配置都从它对齐口径。
✅ 全组按这个口径读图:per-worker 一条线、接口名用短模板、机器用 HOST 变量切。
🚨 口径没对齐的典型事故:日志里搜 /api/v2/me 搜不到;把单机合计 120(30×4)当成单 worker 上限,判定阈值错 4 倍。
# 附 4 条建议告警(口径卡原文): # 1. DB 池打满 2. Redis 池打满 # 3. 5xx 速率 > 1 4. deriv(RSS[1h]) > 5MB/h
联动:主图谱 b0 口径说明 ↗ · 相关课程:job / instance / label
它是什么:biz 每个接口每秒被叫几次。Counter 按 per-worker 精确抓取、无漏计,按 handler(子路由模板)拆开——这是 biz 的「客流表」。
回答什么问题:谁是最忙接口?流量结构有没有突变?调用方还活着吗?
✅ 曲线随业务节奏平滑起伏,各接口比例稳定(对照核心盘 c16 的读法)。
🚨 无缘无故突增 = 被刷/爬虫(去按 handler 定位并限流);某接口流量归零 = 上游调用方挂了(先问调用方再查自己)。
# 面板口径:每秒请求数按 handler 拆
sum(rate(siliang_biz_http_requests_total[5m])) by (handler)
联动:主图谱 b1 ↗ · 相关课程:rate() · HTTP 健康四件套(c16)
它是什么:biz 每秒「服务员自己把菜搞砸」几次,按 handler 拆开。只盯 5xx(服务端的错),4xx 是客人的错不算在内。
回答什么问题:现在有没有事故?哪个接口在出事故?
✅ 长期贴地(0 附近小幅波动)。
🚨 > 1 req/s 就该排查——biz 的 5xx 多半源自 DB / Redis / 依赖故障,先看 b5/b7 池面板,再按 handler 直奔日志。
# 面板口径:只数 5xx(正则 status=~"5..") sum(rate(siliang_biz_http_requests_total{status=~"5.."}[5m])) by (handler)
联动:主图谱 b2 ↗ · 相关课程:5xx vs 4xx · HTTP 健康(c17)
它是什么:此刻 biz 店里多少客人没走(在途请求),按 method 拆。它是铁三角里的「并发」:并发 ≈ QPS × 延迟。
回答什么问题:请求在不在排队?变慢是流量变大还是处理变慢?
✅ 随 QPS 同步起伏,流量落下去它也跟着落。
🚨 持续高位 = 请求在排队——多半是 DB 慢查询或依赖抖动的旁证,配合 b5/b7 池面板一起看。
# 面板口径:在途请求数按 method 拆
sum(siliang_biz_http_requests_in_progress) by (method)
联动:主图谱 b3 ↗ · 相关课程:QPS·并发·延迟铁三角 · HTTP 健康(c18)
它是什么:biz 最慢的 5 个接口排行榜(P95,单位秒)。要 P99 就进查询把 0.95 改成 0.99——面板默认比核心盘 top10 更聚焦,就是 biz 的性能优化清单。
回答什么问题:该先优化哪个接口?优化之后真的变快了吗?
✅ 榜单相对稳定,优化后对应接口的名次/数值下降。
🚨 新接口突然登顶且持续走高等于「最慢的 5% 用户在等它」——先查它是不是在等 DB/Redis(对 b5/b7 互证)。
# 面板口径:从延迟直方图水桶插值算 P95,topk 只留前 5
topk(5, histogram_quantile(0.95,
sum(rate(siliang_biz_http_request_duration_seconds_bucket[5m])) by (le, handler)))
联动:主图谱 b4 ↗ · 相关课程:P50/P95/P99 · 直方图水桶与 topk
它是什么:biz 的 DB 图书馆借书证使用率 = 借出 ÷ 藏书(pool 10 + overflow 20 = 30,每 worker 一份)。80% 黄 / 95% 红。
回答什么问题:数据库连接是不是快借光了?排队是不是池引起的?
✅ 有涨有落的锯齿,高峰冲一把又回落。
🚨 贴 100% = 池打满、请求开始排队;哪台机器打满看 repeat 出来的两张图(HOST 变量切机器),再去查慢查询。
# 面板口径:使用率(分子 checked_out,分母 capacity) max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m]) / max_over_time(siliang_biz_db_pool_capacity[1m])
联动:主图谱 b5 ↗ · 相关课程:连接池:图书馆借书 · 连接池台账(m115/m116)
它是什么:上一张的绝对值版——各 worker 实际借了几条 + 上限 30 的水平参考线。百分比告诉你「比例」,绝对值告诉你「离天花板还剩几条」。
回答什么问题:现在到底借出去多少条?还剩多少余量给高峰?
✅ 借出线在参考线下方呼吸起伏,回落干脆。
🚨 借出线贴住 30 的参考线不下来 = 池打满实锤(和 b5 的 100% 互证),马上找占着连接不还的慢查询。
# 面板口径:借出数 + 上限参考线(max 取容量) siliang_biz_db_pool_connections{state="checked_out"} + max(siliang_biz_db_pool_capacity)
联动:主图谱 b6 ↗ · 相关课程:连接池:图书馆借书 · 连接池台账(m116)
它是什么:biz 的 Redis 借书证使用率。特别注意:这个 Redis 与 Agent 服务共享——对方用得多也会挤占你的池子,这是一张「合租的水电表」。
回答什么问题:Redis 连接是不是成了瓶颈?紧张是我们自己还是室友造成的?
✅ 低水位平稳波动,随业务节奏小幅呼吸。
🚨 使用率持续走高:biz 没发版也要先想室友(Agent 服务)与共享上限 50;确认时段相关性后再下钻归因。
# 面板口径:Redis 使用率(in_use ÷ capacity) max_over_time(siliang_biz_redis_pool_connections{state="in_use"}[1m]) / max_over_time(siliang_biz_redis_pool_capacity[1m])
联动:主图谱 b7 ↗ · 相关课程:连接池:图书馆借书 · 连接池台账(m117/m118)
它是什么:Redis 池的绝对值版——各 worker 借出几条 + max_connections=50 参考线。和 b6 一样:使用率看趋势,绝对值看「离天花板多远」。
回答什么问题:全机一共借出去几条 Redis 连接?50 的上限还剩多少余量?
✅ 借出线远低于 50,且能随流量回落。
🚨 贴近 50 = 合租的图书馆快没书可借(连 biz 带 Agent 一起算)——连接开始等待,接口延迟跟着抖。
# 面板口径:借出数 + 上限参考线 siliang_biz_redis_pool_connections{state="in_use"} + max(siliang_biz_redis_pool_capacity)
它是什么:biz 每个 worker 的体温计。每台机器一张图、每 worker 一条 RSS 线(1 分钟窗口最大值抹抓取抖动);VMS 是虚拟地址空间,虚高只做参考。注意它只反映被 Prometheus 抓到的 worker。
回答什么问题:biz 的内存健康吗?是正常呼吸还是真在偷偷涨?
✅ 锯齿 = 健康;冲高后平台期 = allocator 不归还(Python 常态,内存留着复用,重启才还)。
🚨 线性爬升永不回落 = 真累积(泄漏)——用 deriv 量化斜率,配合 b10 的 GC 面板互证,再对照流量(b3)排除「单纯业务变忙」。
# 面板口径:1 分钟窗口最大值,每 worker 一条线 max_over_time(siliang_biz_process_resident_memory_bytes[1m]) # 判「真累积」:deriv(...[1h]) 每小时涨多少字节(> 5MB/h 是口径卡告警线)
联动:主图谱 b9 ↗ · 相关课程:泄漏判定心法 · RSS / VMS / working_set · 内存全局水位(m101)
它是什么:biz 的垃圾车出车频率(按代拆)。gen0/1 新生代周转快、出车勤属正常;gen2 老年代出车少也正常,但「长期不出车」就有问题了。
回答什么问题:内存涨是「活多垃圾多(正常)」还是「大对象赖着不走(驻留)」?
✅ gen0/1 频繁出车、gen2 偶尔出车,整体节奏稳定。
🚨 gen2 长期停滞而 gen0/1 活跃 = 有大对象驻留——与 b9 的 RSS 平台期/爬升互证后,按泄漏思路下钻。
# 面板口径:各代回收速率
sum by (generation) (rate(siliang_biz_python_gc_collections_total[5m]))
联动:主图谱 b10 ↗ · 相关课程:Python GC 分代回收 · 进程资源四件套(m126)
用户反馈业务接口报错。biz 的 5xx 多半不是代码炸了,而是 DB / Redis / 依赖故障连坐——顺着「错误率 → 池 → 日志」的链走。
# 第 1 步:哪个接口在出 500?(b2) sum(rate(siliang_biz_http_requests_total{job="siliang-biz", status=~"5.."}[5m])) by (handler) # > 1 req/s 即进入排查;锁定的 handler 去日志搜(记得去掉 /api/v2 前缀) # 第 2 步:是不是 DB 池打满连坐?(b5/b6) max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m]) / max_over_time(siliang_biz_db_pool_capacity[1m]) # 贴 1.0(100%)= 请求在等连接 → 去找占着连接的慢查询 # 第 3 步:是不是 Redis 挤占?(b7,注意与 Agent 服务共享上限 50) siliang_biz_redis_pool_connections{state="in_use"} # 贴近 50 = 合租池紧张 → 看 b8 的机器分布,确认是 biz 还是室友
判读:池使用率回落后 5xx 跟着归零 = 池连坐实锤;池没满但 5xx 不断 = 去依赖(DB/Redis 本身)查故障。
「最近有点慢」的模糊反馈。先让 b4 榜单告诉你该怀疑谁,再用 b3/b5 区分「排队慢」还是「处理慢」。
# 1) 最慢的 5 个接口(b4;想看最惨的 1% 就把 0.95 改 0.99) topk(5, histogram_quantile(0.95, sum(rate(siliang_biz_http_request_duration_seconds_bucket[5m])) by (le, handler))) # 2) 并发是否高位不落(b3)——持续高位 = 在排队 sum(siliang_biz_http_requests_in_progress) by (method) # 3) 按 worker 拆池借出数(b6 变体)——单 worker 打满会被求和掩盖 sum by (instance) (siliang_biz_db_pool_connections{state="checked_out"}) # 某个 instance 独自顶到 30 = 那家「分店」的池被打满
判读:榜单头部接口 + 并发高位 + 池贴顶三者同时出现 = 慢查询占连接的连锁反应;只有榜单高、池空闲 = 接口自身逻辑或外部调用慢。
有人盯着 b9 喊内存涨。先别慌——三种形态三种结论,用形态和斜率说话。
# 1) 看形态(b9):每 worker 一条线 max_over_time(siliang_biz_process_resident_memory_bytes[1m]) # 锯齿/冲高回落 = 健康;冲高后横走 = 平台期(allocator 不归还,常态) # 斜着向上永不回落 = 真累积,进入第 2 步 # 2) 量化斜率:每小时涨多少字节 deriv(siliang_biz_process_resident_memory_bytes[1h]) # 持续 > 5MB/h = 口径卡的「持续爬升」告警线(以 b0 口径卡为准) # 3) GC 互证(b10):gen2 停滞而 gen0/1 活跃 = 大对象驻留 sum by (generation) (rate(siliang_biz_python_gc_collections_total[5m])) # 4) 排除「单纯变忙」:流量同步涨了吗? sum(siliang_biz_http_requests_in_progress) by (method)
判读:deriv 持续超线 + gen2 停滞 + 流量没涨 = 泄漏,按内存盘课程下钻;流量同涨 = 先按容量问题处理。
口径卡附了 4 条建议告警:DB 池打满 / Redis 池打满 / 5xx 速率 >1 / RSS 持续爬升。照着口径写进告警规则文件。
groups: - name: siliang-biz # 口径卡 b0 的 4 条建议告警 rules: - alert: BizDbPoolNearFull expr: max_over_time(siliang_biz_db_pool_connections{state="checked_out"}[1m]) / max_over_time(siliang_biz_db_pool_capacity[1m]) > 0.95 for: 5m # 95% 红:池打满的前最后窗口 - alert: BizRedisPoolNearFull expr: max_over_time(siliang_biz_redis_pool_connections{state="in_use"}[1m]) / max_over_time(siliang_biz_redis_pool_capacity[1m]) > 0.95 for: 5m # 注意与 Agent 服务共享上限 50 - alert: BizHttp5xxRate expr: sum(rate(siliang_biz_http_requests_total{status=~"5.."}[5m])) > 1 for: 5m # > 1 req/s:按 handler 直奔日志 # 第 4 条:deriv(siliang_biz_process_resident_memory_bytes[1h]) > 5MB/h # 阈值口径以 b0 口径卡原文为准
判读:告警触发后,处理路径分别是「查慢查询 / 查室友 Agent / 按 handler 查日志 / 按泄漏链下钻」。
没有发版、没有活动,b1 上某个 handler 的线突然竖起来——要么被刷,要么某接口突然消失(调用方挂了)。两种都要按 handler 处理。
# 1) 突增定位:谁在涨?(b1,临时把窗口改小看细节) sum(rate(siliang_biz_http_requests_total[1m])) by (handler) # 2) 近 1h 该 handler 总量对照(increase 看累计) sum by (handler) (increase(siliang_biz_http_requests_total[1h])) # 3) 谁消失了:对比各 handler 速率(归零的 = 调用方挂了) sum(rate(siliang_biz_http_requests_total[5m])) by (handler) # 无发布无活动却突增 = 被刷/爬虫:按 handler(短模板名)去限流/封禁
判读:突增的 handler 若伴随 5xx(b2)与池紧张(b5),先限流止血再查来源;归零的接口则先呼调用方。
# 错: # grep "/api/v2/me" → 图例名 ≠ 完整路径,搜不到 # 对: # grep " /me " → handler 是子路由模板(口径卡 b0)
# 错: sum(siliang_biz_db_pool_connections{state="checked_out"}) # → 4 worker 加总,掩盖单打满 # 对: sum by (instance) (siliang_biz_db_pool_connections{state="checked_out"})
# 错: # b7 高 → 只查 biz 代码(合租水电表,只查一户没用) # 对: # b7 高 → 先想共享(Agent 服务),再对照时段归因
siliang_biz_process_resident_memory_bytes 是 Gauge(温度计),Gauge 求 rate 无意义。正解:看形态直接读数;判「真累积」用 deriv 算斜率。 # 错: rate(siliang_biz_process_resident_memory_bytes[5m]) # → Gauge 求 rate # 对: deriv(siliang_biz_process_resident_memory_bytes[1h]) # → 每小时涨多少字节
# 错: # 平台期 → 判泄漏 → 回滚(白折腾) # 对: # deriv(...[1h]) 持续 > 5MB/h 才按泄漏链下钻
# 错: # 只读一张卡就下结论 # 对: # b5(使用率)+ b6(借出 vs 上限 30)成对看
# 错: # 只见一张 → 报「数据丢失」 # 对: # repeat 成对出现;HOST 变量切机器前缀查看
siliang_backend_worker 的指标当成 biz 的,数值对不上面板。原因:biz 全部指标挂在 job="siliang-biz" 下,两套服务不共享指标。正解:查 biz 永远带 job="siliang-biz" 过滤(镜像的是「图」,不是「数据」)。 # 错: sum(rate(siliang_http_requests_total[5m])) # → 这是 backend 的指标 # 对: sum(rate(siliang_biz_http_requests_total[5m])) # → biz 的指标带 biz_ 前缀
参考答案:biz 盘不发明新东西,就是把核心盘教过的体检项目(HTTP 四件套、连接池两对卡、RSS/GC)在 biz 服务上原样再量一遍,读法完全复用。映射表任意三对:b1↔c16(QPS)、b5/b6↔m115/m116(DB 池两张卡)、b9↔m101(RSS 形态)、b10↔m126(GC 速率)等。
参考答案:先想「合租」——这个 Redis 与 Agent 服务共享(上限 max_connections=50),室友用得多也会挤占你的池子,对照时段相关性确认是不是 Agent 的繁忙时段。再查 biz 自己:流量是否突增(b1)、是否有新接口在狂用 Redis。共享池是全机额度,谁的锅要看谁的时间线吻合。
参考答案:线性爬升永不回落 = 真累积(泄漏):用 deriv(RSS[1h]) 量化,持续大于 5MB/h(口径卡告警线)即立案,配合 b10 看 gen2 是否停滞、按内存盘课程下钻找凶手。冲高后平台期 = allocator 不归还,是 Python 的常态(内存留着复用,重启才还),不是泄漏——盯住 deriv 不超线即可,不用半夜回滚。
参考答案:① DB 池打满——查占着连接不还的慢查询;② Redis 池打满——先想与 Agent 服务共享(对方挤占),再查自己;③ 5xx 速率 > 1 req/s——按 handler(短模板名)直奔日志,顺 b5/b7 看是不是池连坐;④ deriv(RSS[1h]) > 5MB/h——按泄漏链下钻(形态 → GC 互证 → 流量对照)。