门面生意好不好,看四件:QPS(谁最忙)/ 5xx(谁在出事)/ 并发(在不在排队,ops 排空口径盯发布)/ P99 Top10(优化待办清单)。覆盖核心盘 🟢 组 4 张面板(c16–c19)· backend(FastAPI · 4 worker)。
把 backend 想成一家奶茶店:c16 是进店速度(每个柜台每秒接几单),c17 是服务员自己把菜搞砸的次数(5xx,按柜台拆开),c18 是此刻店里有多少客人没走(在途请求,发布时看旧员工有没有把手上单子做完再走),c19 是全店最慢的十个柜台排行榜(你的性能优化待办清单)。
四张图不是孤立的:铁三角(并发 ≈ QPS × 延迟)把它们串成一条因果链——某依赖变慢 → 请求排队 → 并发抬升 → 池打满 → 5xx 雪崩。所以看门面生意永远四件套连看,单看一张最容易误判。
先分清两组数。"每分钟进来多少个客人"是 QPS(进店速度),"店里此刻有多少人在等咖啡"是并发(在途请求)——这是两个完全不同的东西:店里人满为患,可能是客人来得太猛(QPS 涨),也可能是做咖啡变慢了(延迟涨),分不清这两种就没法下结论。利特尔法则把它们连起来:店里人数 = 进店速度 × 每人待的时间。
再看"搞砸"的归责。4xx 是客人的点单方式不对——参数写错、没登录就下单,服务员把单子退回去就行,不用半夜爬起来;5xx 是后厨自己炸了——代码抛异常、DB 锁冲突、Redis 或 LLM 依赖挂了。所以错误率面板只盯 5xx,而且按 handler(柜台号)拆开:哪个柜台在搞砸,直奔哪个柜台的日志。口径卡给的行动线很具体:>1 req/s 就该排查了。
最后是排行榜和发布。c19 用 topk(10, …) 把全站最慢的十个接口挑出来——这就是你的性能优化待办清单,优化完再来看同一条查询,榜单名次的变化就是成效。c18 还多一条"ops 排空口径"参考线,专盯发布:滚动发布时,旧 worker 手上的在途请求应该逐个归零(旧员工把手上的单做完再下班);发布完还挂着不归零,就是排空卡住。而雪崩链在暗中随时待命:延迟翻倍 → 并发翻倍 → 连接池打满 → 排队更狠 → 5xx 铺开——这就是为什么"慢"从来不是小事。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 每个柜台每秒接几单 | c16 HTTP QPS(按 handler):handler=路由模板(柜台号,不含 /api/v2 前缀) |
| 服务员自己把菜搞砸 | c17 5xx 错误率:status=~"5..",>1 req/s 就该排查,按 handler 直奔日志 |
| 店里此刻的客人数 | c18 在途请求(Gauge):in_progress + ops 排空口径;持续高位=在排队 |
| 最慢柜台排行榜 | c19 P99 Top10:topk(10, histogram_quantile(0.99, …))——性能优化待办清单 |
| 顾客把优惠券递反了 | 4xx=客人的错(参数不对/没登录),一般不用半夜爬起来;面板只盯 5xx |
| 打烊交接:单做完才走 | 滚动发布:旧 worker 在途数应逐个归零(ops 排空口径);不归零=排空卡住 |
# 按 handler 拆 = 每个柜台一条线(c16 口径) sum(rate(siliang_http_requests_total[5m])) by (handler)
# Counter 必配 rate(c16 口径)
sum(rate(siliang_http_requests_total[5m])) by (handler)# 只筛 5xx:正则 5.. 匹配 500/502/503/504…(c17 口径) sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler)
# 错:只盯 500 一种;对:正则覆盖全部 5xx {status=~"5.."} # 500 · 502 · 503 · 504 全算
# Gauge 直接读(c18 口径,含 ops 排空线)
sum(siliang_http_requests_in_progress) by (method)
+ sum(siliang_ops_http_in_flight)# 发布验证:by (instance) 变体逐 worker 看(c18 扩写)
sum(siliang_http_requests_in_progress) by (instance)# le 必进 by(c19 口径)
histogram_quantile(0.99, sum(rate(siliang_http_request_duration_seconds_bucket[5m])) by (le, handler))# 榜单套在分位数外面(c19 口径) topk(10, histogram_quantile(0.99, …))
它是什么:backend 每个接口每秒被调几次。handler 是路由模板(接口的"柜台号",不含 /api/v2 前缀),请求总数是 Counter,配 rate 按 handler 拆开——这就是门面生意的"客流结构图"。
回答什么问题:谁是最忙接口?流量结构有没有突变?
✅ 各 handler 量级随业务节奏稳定,占比结构平稳
🚨 无发布无活动却突增 = 被刷/爬虫;某接口流量归零 = 上游调用方挂了。动作:突增先查时段(发布/活动),归零顺着调用链找上游。
# 面板 PromQL(照抄主图谱口径) sum(rate(siliang_http_requests_total[5m])) by (handler)
联动:主图谱 c16 ↗ · 相关课程:rate():算速度 · job / instance / label
它是什么:每秒有多少次"服务员自己把菜搞砸了"——服务端错误(status=~"5..")的速率,按 handler 拆开。它是事故的第一现场:现在有没有事故、哪个接口在出事故,看它最快。
回答什么问题:现在有没有事故?哪个接口在出事故?
✅ 各 handler 的 5xx 速率长期贴近 0,偶发成刺
🚨 >1 req/s 就该排查(DB / Redis / 依赖故障),按 handler 直奔日志。动作:主图谱排查速查表第一行——5xx → 锁冲突(c34/c35)→ LLM 错误率(c13)找连坐源头。
# 面板 PromQL(照抄主图谱口径) sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler)
联动:主图谱 c17 ↗ · 相关课程:5xx vs 4xx:谁的错 · 数据库锁冲突(5xx 常见源头)
它是什么:此刻店里有多少客人没走——已到达还没返回的在途请求(Gauge,按 method 拆)。它还多一条"ops 排空口径"参考线,专盯发布:旧 worker 手上的请求必须做完才能退场。
回答什么问题:请求在不在排队?发布时旧 worker 有没有干净退场?
✅ 随业务节奏起伏,峰值后回落;滚动发布时每个 worker 的在途数应逐个归零
🚨 持续高位 = 排队(DB 慢查询或依赖抖动);发布后不归零 = 排空卡住。动作:配合铁三角定位是"来得太多"还是"走得太慢",再看池面板(m115)有没有打满。
# 面板 PromQL(照抄主图谱口径)
sum(siliang_http_requests_in_progress) by (method)
+ sum(siliang_ops_http_in_flight)
联动:主图谱 c18 ↗ · 相关课程:QPS·并发·延迟铁三角 · 连接池台账(排队的终点)
它是什么:全站最慢的 10 个接口排行榜——P99 是"最惨的 1% 请求"的等待时间,延迟从 _seconds_bucket 水桶里插值算出,再用 topk(10, …) 只画最慢前十。它不是事故面板,是性能优化待办清单。
回答什么问题:该优化哪个接口?优化后变快了吗?
✅ 榜单稳定、名次随优化有序变化;没有"莫名其妙冒出来的新榜首"
🚨 榜首 P99 突然整体抬升 = 该接口在恶化。动作:进排查链——c18 看排队、m115 看池、c15 看 LLM(等模型);优化上线后用同一条查询对比成效。
# 面板 PromQL(照抄主图谱口径) topk(10, histogram_quantile(0.99, sum(rate(siliang_http_request_duration_seconds_bucket[5m])) by (le, handler)))
联动:主图谱 c19 ↗ · 相关课程:P50/P95/P99:排队 · 直方图水桶与 topk
行动线来自核心盘口径卡(>1 req/s 排查),按 handler 拆开让告警直接指到柜台。
# 告警规则:5xx 速率(阈值来自核心盘 c17 口径卡) groups: - name: siliang-http rules: - alert: Backend5xxRateHigh expr: sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler) > 1 for: 5m # 持续 5 分钟才报,避开偶发成刺 labels: severity: critical annotations: summary: "handler {{ $labels.handler }} 5xx 超 1 req/s —— 直奔该 handler 日志"
判读收尾:告警带上 handler 名,值班同学不用猜"是哪儿炸了"。
主图谱排查速查表第一行的完整路径:5xx → 锁冲突 → LLM 连坐。
# 1) 哪个 handler 在出事(c17 口径) sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler) # 2) 是不是 DB 锁连坐(c35 口径:operation × code,1205/1213) sum(rate(siliang_db_lock_errors_total[5m])) by (operation, code) # 3) 是不是 LLM 依赖连坐(c13 口径) rate(siliang_llm_calls_total{outcome="error"}[5m]) by (provider) # 判读:三张图对时间戳——谁先冒头谁是源头,别急着回滚
主图谱排查速查表第二行:谁最慢 → 在不在排队 → 池打满没有。
# 1) 谁最慢(c19 口径) topk(10, histogram_quantile(0.99, sum(rate(siliang_http_request_duration_seconds_bucket[5m])) by (le, handler))) # 2) 在不在排队(c18 口径) sum(siliang_http_requests_in_progress) by (method) + sum(siliang_ops_http_in_flight) # 3) DB 池打满没有(m115 口径:借出 ÷ 藏书,95% 红) max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on(instance,pool) max_over_time(siliang_db_pool_capacity[1m]) # 4) 还要排除:LLM 在拖(c15)与容器 CPU 挤占(k3)
滚动发布时,旧 worker 手上的在途请求应逐个归零——这是"死得干不干净"的标准。
# 发布窗口内盯:按 instance 逐 worker 看(c18 口径的 by 变体) sum(siliang_http_requests_in_progress) by (instance) + sum(siliang_ops_http_in_flight) by (instance) # 健康形态:每个旧 worker 的在途数在发布后几分钟内依次归零 # 危险形态:某 worker 的线挂住不归零 = 排空卡住(查慢请求/长连接) # 配套确认:跨进程收尸(c11)——旧实例退场有没有留下烂尾任务
性能优化的完整闭环:榜单定目标 → 优化 → 同一条查询验证成效。
# 优化前:锁定目标(c19 口径) topk(10, histogram_quantile(0.99, sum(rate(siliang_http_request_duration_seconds_bucket[5m])) by (le, handler))) # 优化后:跑同一条查询,看该 handler 的 P99 是否整体下移 # 判读:名次下降/绝对值下降 = 有效 # 被别的 handler 顶上榜首 = 达标,翻下一页待办
# 错: 把 4xx 计入告警错误率 # 对: sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler)
# 错: {status="500"} # → 漏 502/503/504 # 对: {status=~"5.."} # → 全部 5xx
# 错: avg(rate(...)) / avg 折线看延迟 # 对: topk(10, histogram_quantile(0.99, … by (le, handler)))
# 错: sum(rate(..._duration_seconds_bucket[5m])) # 对: sum(rate(..._duration_seconds_bucket[5m])) by (le, handler)
# 错: c18 高 → 直接扩容 # 对: c16 没涨 + c18 高 = 走得慢 → 去 c19/m115 找慢因
# 错: 发布后在途不归零 → 判连接泄漏 # 对: sum(siliang_http_requests_in_progress) by (instance) → 找挂住的 worker 查慢请求
# 错: QPS 翻倍 → 直接封 IP # 对: 对照发布/活动日历 → 无事件才按被刷/爬虫处置
# 错: handler 线归零 → 忽略 # 对: 主力 handler 归零 → 上游调用方挂了,立即按故障排查
参考答案:铁三角——并发 ≈ QPS × 延迟(店里人数 = 进店速度 × 每人待多久)。并发高有两种成因:客人来得太多(QPS 涨)或做单变慢(延迟涨)。所以 c18 冲高必须回看 c16(量)和 c19(慢),分清病因才能决定是扩容还是优化接口。
参考答案:4xx 是客人的错——参数不对、没登录,服务员退回重递即可,不用半夜爬起来;5xx 是店里的错——代码异常、DB 锁冲突、Redis/LLM 依赖故障。监控要抓的是"我们自己的问题",所以错误率面板只盯 5xx;而且 >1 req/s 就该按 handler 直奔日志。
参考答案:ops 排空口径下,每个旧 worker 的在途请求数应逐个归零——旧员工把手上的单做完再下班,谁先做完谁先走。发布完还挂着不归零 = 排空卡住,多半是该 worker 手上有慢请求或长连接没结束,去 c19 找慢接口、查长连接。
参考答案:它是性能优化待办清单——榜首就是要优化的接口,优化完跑同一条查询对比名次就是成效验证。P99 比 averages 诚实是因为平均值会被海量快请求稀释:一个 2s 的请求混在 999 个 80ms 里,平均几乎不动,但 P99 会把它如实暴露出来。