每个 HTTP 状态码都在回答同一个问题:这事怪谁。4xx=客人点单方式不对(不用半夜爬起来),5xx=后厨自己炸了(立即排查)。所有错误率面板只盯 5xx,>1 req/s 就该看日志——口径见 c17(backend)/ b2(biz)两张图。
把服务想象成一家餐厅:4xx 是客人点单方式不对——优惠券递反了、没带会员卡、点了菜单上没有的菜,服务员退回重递就好;5xx 是后厨自己炸了——灶台灭火、供应商断货、厨房着火。前者是客人的功课,后者才是店家的故障。
所以监控有明确的站队:所有错误率面板只盯 5xx(正则 status=~"5.."),因为只有它代表"店里出了事";阈值经验是 >1 req/s 就该看日志。四两的 5xx 多半不是自己代码写错,而是依赖连坐——DB 锁冲突、Redis/LLM 依赖故障、连接池打满排队,三种来源各有专门面板可确认。
每次 HTTP 请求结束,服务器都会给一个三位数的状态码,第一位数代表"这事的结果归谁":2xx 成功上菜;3xx 换个窗口取餐(跳转);4xx 是客人的点单方式有问题——递错了优惠券(400 参数错)、没带会员卡(401/403 没登录/没权限)、点了菜单上没有的菜(404 路径不存在)。这些请求服务器处理得清清楚楚、礼貌拒绝,不是故障。
5xx 完全不同:客人点单没问题,后厨自己出事了——代码抛异常(500)、上游供应商断货(DB/Redis/LLM 依赖故障)、后厨忙到瘫痪(池打满排队超时)。这才是"店里的错",是半夜电话的来源。所以错误率面板的正则只写 status=~"5..":把 4xx 算进错误率,等于把"客人递错优惠券"也记成"后厨着火",告警会天天狼来了。
四两实战里,5xx 冒头先别急着怀疑自己的代码——按主图谱 triage 的顺序溯源:① 看 c17 哪个 handler 在报 5xx → ② 看 b2 是不是 biz 侧 → ③ 查三大来源:DB 锁冲突(1205 锁等待超时 / 1213 死锁,看 c34/c35)、LLM 供应商失约(错误率 >10% = 限流 429 或 key 失效,看 c13)、连接池打满连坐(借出÷藏书贴 100%,看 m115/b5)。另有一个经典事故现场要会认:容器被 OOM 杀掉重启时,会同时出现"容器重启 + 一波 5xx + 用户掉线"三联。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 优惠券递反了 | 400 参数错误:请求参数不对,退回重递即可,不是故障 |
| 没带会员卡就想进 VIP 区 | 401/403 未登录 / 无权限:鉴权拦住,礼貌拒绝,不用修代码 |
| 点了菜单上没有的菜 | 404 路径不存在:URL 写错,属于调用方的功课 |
| 后厨着火 | 500 内部错误:代码异常,5xx 的一种,要立即排查 |
| 供应商断货 / 煤气停了 | 依赖故障连坐:DB 锁冲突(1205/1213)、Redis/LLM 挂了,5xx 的常见真凶 |
| 经理只看"厨房事故记录本" | 错误率面板只盯 5xx:status=~"5..",>1 req/s 就看日志(c17/b2 口径) |
2xx 成功、3xx 跳转、4xx 客人的错、5xx 店里的错。看监控先看首位数字——它直接决定"要不要半夜爬起来"。 # 一条成功请求的完整记录长这样 siliang_http_requests_total{handler="/me", status="200"}
# 4xx 明细:不告警,但值得偶尔看一眼分布 sum(rate(siliang_http_requests_total{status=~"4.."}[5m])) by (status)
# c17 主口径:只数 5xx,按 handler 拆开 sum(rate(siliang_http_requests_total{status=~"5.."}[5m])) by (handler)
status=~"5.." 用正则匹配所有 5 开头的两位后缀(500/502/503/504…)。写死 status="500" 会漏掉网关超时等其他 5xx。 # 错: {status="500"} 只逮一种 对: {status=~"5.."} 全逮 {status=~"5.."} # 正则匹配:5 开头 + 任意两位
# 行动线(c17/b2 口径原文) # > 1 req/s:直奔该 handler 的日志
# 溯源三连(对时间戳一起看) sum(increase(siliang_db_lock_errors_total[1h])) # ① 锁 rate(siliang_llm_calls_total{outcome="error"}[5m]) # ② LLM max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) # ③ 池
# 5xx 尖峰时刻 × 容器重启次数对表(k5 口径) changes(container_start_time_seconds[1h])
siliang_biz_http_requests_total。两张图一起看,还能区分"backend 炸了还是 biz 炸了"。 # b2 主口径 sum(rate(siliang_biz_http_requests_total{status=~"5.."}[5m])) by (handler)
🟢 核心盘 · HTTP 5xx 错误率(c17)↗——本课的主面板:每秒多少次"服务员自己把菜搞砸了",按 handler 拆开;🚨 >1 req/s 就该排查(DB / Redis / 依赖故障),按 handler 直奔日志。
🟢 biz 盘 · HTTP 5xx 错误率(b2)↗——biz 侧同款口径;biz 的 5xx 多半源自 DB / Redis / 依赖故障。与 c17 对照还能快速分流"backend 炸还是 biz 炸"。
🔴 核心盘 · 数据库锁冲突率(c35)↗——5xx 的头号内源性来源:1205=锁等待超时(等太久放弃)、1213=死锁(互相等,数据库强滚一个)。按 operation × code 拆开看谁在抢锁。
🟡 核心盘 · LLM 错误率(c13)↗——依赖连坐的另一路:>10% = provider 限流(429)或 key 失效,切流量/换 key,不是我们代码的 bug。以及主图谱 🚑 排查速查表 ↗ 第一行「接口报 500 / 用户炸群」就是本课 SOP。
每天巡检把 c17 的曲线扫一眼,再手跑一遍它的口径,确认图和查询理解一致。
# c17 口径:每秒多少次服务端错误,按 handler 拆开 sum(rate(siliang_http_requests_total{job="siliang_backend_worker", status=~"5.."}[5m])) by (handler) # 判读:全部贴地(≈0)= 健康; # 某条线抬升但 < 1 req/s = 记录观察; # 某条线 > 1 req/s = 按 handler 直奔日志排查
告警规则里只写 5xx——把 4xx 算进去只会制造狼来了。行动阈值用面板口径的 1 req/s。
groups: - name: siliang-http-5xx rules: - alert: BackendHTTP5xxRate # 只数 5xx;> 1 req/s = 面板口径的行动线 expr: | sum(rate(siliang_http_requests_total{job="siliang_backend_worker", status=~"5.."}[5m])) by (handler) > 1 for: 5m # 持续窗口为通用示例,线上以核心盘口径卡为准 labels: severity: critical annotations: summary: "handler {{ $labels.handler }} 5xx > 1 req/s:按 triage 溯源三连排查"
5xx 冒头后不要先翻代码,先回答"是不是依赖连坐"。三条查询拉同一时间窗,谁同时在动谁就是嫌疑人。
# 嫌疑一:DB 锁冲突(近 1h 次数,c34 口径) sum(increase(siliang_db_lock_errors_total{job="siliang_backend_worker"}[1h])) # 嫌疑二:LLM 供应商失约率(c13 口径,>10% = 限流/key 失效) rate(siliang_llm_calls_total{outcome="error"}[5m]) / clamp_min(rate(siliang_llm_calls_total[5m]), 1) # 嫌疑三:DB 池打满(m115 口径,贴 100% = 请求排队超时) max_over_time(siliang_db_pool_connections{state="checked_out"}[1m]) / on (instance, pool) max_over_time(siliang_db_pool_capacity[1m])
c17 与 b2 一起看,10 秒钟分清 backend 还是 biz 的问题——这是分店定位的价值。
# b2 口径:biz 每秒搞砸几次,按 handler 拆 sum(rate(siliang_biz_http_requests_total{status=~"5.."}[5m])) by (handler) # 分流判读: # c17 冒头 b2 平静 → backend 侧(LLM/WS/任务连坐为主) # b2 冒头 c17 平静 → biz 侧(多半 DB / Redis 依赖) # 两边同时冒头 → 找共同依赖:DB 锁冲突 / Redis 抖动
4xx 不进错误率告警,但按 status 拆开看分布,能发现"被刷"和"上游改参数"这类事。
# 4xx 明细:按具体状态码拆开(400/401/403/404) sum(rate(siliang_http_requests_total{status=~"4.."}[5m])) by (status, handler) # 判读:401/403 突增 = 有人拿过期 token 猛刷(注意来源); # 400 集中在某个 handler = 上游调用方改了参数没对齐; # 404 突增 = 扫描器在猜路径,确认网关有兜底即可
滚动发布期间出现一波 5xx,先看是不是旧 worker 排空的正常涟漪,别急着 revert。
# 在途请求:发布时每个 worker 应逐个归零(c18 ops 排空口径) sum(siliang_http_requests_in_progress) by (method) + sum(siliang_ops_http_in_flight) # 存活点名:每个 service 预期 2(k6 口径) count by (service) (container_start_time_seconds{service=~"siliang-.*"}) # 判读:在途逐 worker 归零 + 存活=2 + 5xx 随后贴地 → 发布涟漪,结案; # 排空卡住不归零 或 存活<2 → 真问题,按 OOM 三联/重启面板继续查
# 错: rate(siliang_http_requests_total[5m]) 不分 status 全算"错误" # → 误报工厂 # 对: rate(siliang_http_requests_total{status=~"5.."}[5m]) # → 只数店里的错
# 错: {status="500"} # → 漏掉 502/503/504 # 对: {status=~"5.."} # → 5 开头全逮
# 错: 尖峰 0.2 req/s → 拉群排查 # → 灰尘级事件兴师动众 # 对: > 1 req/s 或持续抬升 → 按 triage 排查 # → 行动线明确
# 错: sum(rate(…{status=~"5.."}[5m])) # → 只知总量 # 对: sum by (handler)(rate(…{status=~"5.."}[5m])) # → 定位接口
# 错: git blame → 改代码 → 上线 # → 代码没问题白忙一场 # 对: 锁 → LLM → 池 三连对时间戳 # → 先分清谁的锅
# 错: siliang_http_requests_total{status=~"5.."} # → 可能双计 # 对: siliang_http_requests_total{job="siliang_backend_worker", status=~"5.."}
# 错: 尖峰 → 立刻 git revert # → 可能只是发布重启涟漪 # 对: changes(container_start_time_seconds[1h]) 对表后再定责
# 错: 4xx 从此再也不看 # → 错过被刷/调用方变更信号 # 对: 巡检看 by (status) 分布,突增再深究 # → 不告警但留眼睛
参考答案:4xx 是用户自己操作不对(参数错、没登录、链接失效),系统在礼貌拒绝,不用修;5xx 是系统自己出故障了(代码异常、数据库/大模型依赖挂了),用户什么都没做错——这才是要我们半夜起来修的事。所以错误率面板只盯 5xx。
参考答案:① 打开 c17 看哪个 handler 冒头、是否 >1 req/s;② 对照 b2 分流是 backend 还是 biz 在炸;③ 跑溯源三连对时间戳——锁冲突(c34/c35:1205/1213)、LLM 错误率(c13:>10%=限流/key 失效)、池使用率(m115:贴 100%=排队超时)。先分清谁的锅再动手。
参考答案:盯 5xx 是因为只有它代表"店里出了事",是告警和排障的对象;4xx 是调用方的功课,算进去只会制造误报。但"不告警"不等于"不看"——巡检时看一眼 4xx 按 status 的分布,401 突增可能是被刷、400 集中可能说明上游改了参数,这是有价值的信号。
参考答案:① DB 锁冲突——1205 锁等待超时 / 1213 死锁,看 c34(近 1h 次数)和 c35(按操作与错误码拆);② Redis / LLM 依赖故障——provider 限流(429)或 key 失效,看 c13(错误率 >10% 红线);③ 连接池打满连坐——借出÷藏书贴 100% 请求排队超时,看 m115(backend)/ b5(biz)。半数以上是依赖连坐,不全是自己代码的锅。