可观测性:平均值 10.5 秒,中位数 1.9 秒
以及 10 个 HTTP 200 里,0 个结果可用
上线之后你会遇到一类最难受的问题:用户说"你们这个有时候特别慢",而你打开仪表盘,一切正常。
看这次实测——40 次真实调用:
平均 10495 ms
P50 1852 ms ← 一半用户比这快
P95 49487 ms ← 20 个人里最惨的那个
最大 49549 ms
平均值是中位数的 5.7 倍。这个"平均 10.5 秒"既不代表大多数人的体验(他们只等了 1.9 秒),也不代表最惨的人(他等了 49 秒)。它谁都不代表。
然后是更隐蔽的一类:10 次调用全部 HTTP 200,0 次结果可用。你的监控面板全绿。
"本店顾客平均等位 10 分钟"——听起来还行。
真相是:90 个人没等,10 个人等了 100 分钟。那 10 个人不会再来了,而你的平均数看不见他们。
一、别看平均,看分位数
平均 10495 ms
P50 1852 ms
P95 49487 ms
P99 49549 ms
→ P95 是平均值的 4.7 倍
平均值对长尾极其敏感:两次 49 秒的请求,就把 40 次调用的平均拉到了 10.5 秒。
该盯的三个数:
| 指标 | 含义 | 用途 |
|---|---|---|
| P50 | 一半用户比这快 | 大多数人的真实体感 |
| P95 | 20 个人里最惨的那个 | 告警阈值定在这里(用户抱怨来自这批人) |
| P99 | 100 个人里最惨的那个 | 容量规划、极端情况排查 |
告警阈值怎么定:先跑一周,拿到你自己的 P95 基线,阈值设在基线的 1.5-2 倍。别拍脑袋定"超过 3 秒告警"——你的 3 秒可能是别人的 P50。
二、指标要能"切开",否则你只知道有人慢
短输入(平均 11 tok,35 次) P50 1214 ms P95 49487 ms 最大 49549 ms
长输入(平均 682 tok, 5 次) P50 2112 ms P95 2359 ms 最大 2359 ms
这个结果很有意思,而且和直觉相反:
- 长输入的中位数确实更慢(2112 vs 1214 ms)——符合预期;
- 但最慢的那两次是短输入(49 秒)——长输入的最大值才 2.4 秒。
如果你只看到总体 P95 = 49 秒,然后想当然地去优化"长输入太慢",你就优化错了对象。这批长尾来自网络/排队抖动,不是输入长度。
所以指标必须能按维度切开:功能、模型、用户分层、输入长度、是否命中缓存、是否降级。光有一条曲线,你只知道"有人慢";切开之后才知道"慢的是谁"。
三、最危险的是"静默失败"
⚠️ 200 但不可用:「我要退货」 → {"category": "退款", "urgent": false, "reason": "用户明确表示要退货,属于退
⚠️ 200 但不可用:「快递没到」 → {"category": "物流", "urgent": true, "reason": "用户反馈快递未到,可能涉及物
…
HTTP 200:10/10 真正可用:0/10
→ 只监控 HTTP 状态码的仪表盘,这里全绿。
原因很朴素:max_tokens 给小了,JSON 被截断——每一条都在 reason 字段中间断掉。HTTP 层完美,业务层全挂。
这是 AI 产品特有的失败模式,传统 Web 监控完全看不见。至少要监控这四类:
| 静默失败 | 怎么发现 |
|---|---|
输出被 max_tokens 截断 | 检查 finish_reason == "length";校验器解析失败(ap3.2) |
| 返回了内容但不符合 schema | 字段齐全性检查(ap3.5) |
| 模型拒答 / 返回空 | 空内容率、拒答率(ap5.4) |
| 走了降级链路却没人知道 | degraded 字段(ap6.1) |
核心动作:在你的日志里,ok(HTTP 层)和 business_ok(业务层)必须是两个字段。仪表盘上要看的是后者。
该记什么:一条结构化日志
{
"ts": "2026-08-04T10:00:00+08:00",
"request_id": "req_a1b2c3", // ap7.1:和响应里的一致 —— 排查的钥匙
"user_id": "u_123", // ap6.4:配额与异常检测
"feature": "qa", // 按功能切维度
"model": "deepseek-chat",
"latency_ms": 1479,
"ttft_ms": null, // ap7.3:流式必记
"in_tokens": 11, "out_tokens": 27,
"cache_hit_tokens": 0, // ap1.4:缓存命中率决定成本
"cost_yuan": 0.000065,
"ok": true, // HTTP 层
"business_ok": true, // ← 业务层:解析成功且字段齐全
"degraded":
这一条日志把前面六章串起来了:ap1.4 的缓存、ap1.6 的成本、ap6.1 的降级、ap6.3 的审核、ap7.1 的 request_id、ap7.3 的 TTFT。一行 JSON,把整个系统的健康状况装进去。
四个必配的告警
| 告警 | 阈值建议 | 为什么 |
|---|---|---|
| 业务成功率下跌 | 低于基线 5 个百分点 | 最重要——它涵盖了所有静默失败 |
| P95 延迟上涨 | 超过基线 1.5-2 倍 | 用户抱怨的直接来源 |
| 单位时间成本异常 | 超过日均 1.5 倍 | 被刷、被注入、或缓存失效(ap6.4) |
| 降级率上升 | 超过 5% | 上游在出问题,你还没感觉到(ap6.1) |
告警要能"值班时看懂":每条告警必须带上是什么、影响多少用户、该看哪个 request_id、上一次正常是什么时候。一条只写"P95 告警"的短信,凌晨三点没有任何用。
🔧 动手做:给你的产品装上仪表(20 分钟)
- 跑本节代码,看平均 10.5 秒 vs 中位数 1.9 秒,和10 个 200 里 0 个可用。
- 把日志改成上面那个结构(一行一条 JSON),尤其是
business_ok和request_id。 - 算出你自己的 P50/P95/P99——用 ap1.6 的流水,一条 SQL 或者十行 Python。
- 加维度:至少能按
feature和degraded切开看。 - 配四个告警,阈值全部基于你自己跑一周得到的基线,不是拍脑袋。
- 写一份告警响应手册:每条告警对应"先看什么、可能是什么、临时怎么止血"——写在告警消息里,不是写在别人找不到的文档里。
✅ 做到这里你应该有:结构化日志 + P50/P95/P99 + 可切维度 + 四个基于基线的告警 + 一份响应手册。
为什么:AP5 教你上线前怎么知道东西好不好,这一节教你上线后怎么知道。两者的区别是:评测集是你选的样本,线上是真实世界给你的样本——它一定会给你你没想到的东西(ap5.6 的回流就靠这些日志)。
而没有可观测性的产品,你只有两种状态:"看起来没事"和"用户投诉了"。中间那段——它正在慢慢变坏而你还有时间修——你完全看不到。
两个常见的"仪表盘失真":
① 采样把长尾采没了。流量大了之后大家会采样(比如只记 1%)。如果是均匀随机采样,P99 基本就废了——最惨的那 1% 很可能一条都没被采到。做法:错误和慢请求 100% 记录,正常请求才采样。
② 维度爆炸。把 user_id 当成指标标签,一百万用户就是一百万条时间序列,监控系统直接被打爆(账单也是)。做法:高基数字段进日志,不进指标标签;指标标签只放低基数维度(feature、model、degraded、business_ok)。
第三个更朴素的坑:告警配了但没人看。上线前请做一次演练——手动触发一次告警,看它多久到达值班人手上。没被验证过的告警链路,等于没有告警。
我的 AI 产品是【描述】,现在的日志字段是【列出】,用的监控工具是【Prometheus/云监控/无】。请帮我:1) 设计结构化日志的完整字段(区分 HTTP 层成功和业务层成功);2) 列出该建的指标和它们的维度标签(注意避免高基数);3) 给出 P50/P95/P99 的计算方式和我该关注的基线;4) 设计 4-6 条告警规则,每条包含触发条件、严重级别、告警消息模板(要带排查线索);5) 写一份告警响应手册的模板,每条告警对应「先看什么、可能是什么、怎么止血」。
可观测性三句话:看分位数别看平均(实测平均是中位数的 5.7 倍)、指标要能按维度切开(长尾竟来自短输入)、ok 和 business_ok 必须分开(10 个 200 里 0 个可用)。
下一节讲改动怎么安全地送出去:灰度与回滚——AP5 的门禁挡住了明显变差的改动,但有些问题只有真实用户能发现。
🔎 来源与核验· 3 条,点开核对
