合成监控和真实用户监控的区别:AI 应用怎么组合拨测与 RUM
合成监控和真实用户监控的区别可以这样概括:合成监控是你自己按固定频率、从固定位置发请求去“试一下”,回答的是“服务现在通不通”;真实用户监控(RUM)是从真实用户的请求里收集数据,回答的是“用户实际用起来怎么样”。前者没有用户也能工作,但只代表探测点所在的网络;后者最贴近体验,但没有流量的时候就是一片空白。
做 AI 应用时,两者之间的空档更明显。上游模型 API 网络是通的,返回的结果却可能变差;流式响应的首个 token 迟迟不来,HTTP 状态码却是 200。下面把两种方式落到 LLM 接口上,再用 cdnping 自己的做法说明怎么组合。
合成监控和真实用户监控分别是什么?
合成监控(Synthetic Monitoring),国内常叫“拨测”或“主动监控”。你写好一个脚本,例如每 5 分钟请求一次某个 API 入口,记录是否成功、耗时多少。请求是“合成”出来的,不是任何用户发起的。
真实用户监控(Real User Monitoring,RUM),也叫“被动监控”。在前端或服务端埋点,把每个真实请求的状态码、耗时、首字节时间等上报。数据量随流量变化,有多少用户就有多少样本。
| 维度 | 合成监控(拨测) | 真实用户监控(RUM) |
|---|---|---|
| 数据来源 | 自己发的探测请求 | 用户真实请求 |
| 什么时候有数据 | 7×24,按固定频率 | 只在有流量时 |
| 代表谁的网络 | 探测节点所在的机房和线路 | 用户自己的网络、设备、地区 |
| 擅长发现 | 上游完全不通、证书过期、DNS 解析失败 | 某地区变慢、某类请求失败、长尾延迟 |
| 盲区 | 测不到用户侧网络;只测固定请求,测不到“答得差” | 低峰期样本少;上线前没有数据 |
| 能否做基线对比 | 能,条件固定,前后可比 | 难,用户构成一直在变 |
| 成本 | 探测频率 × 节点数,可控 | 和流量成正比,要考虑采样 |
放到 AI 应用里,两者各自漏掉什么?
普通网页的监控文章大多讨论首屏时间、LCP 这类指标。AI 应用多了三类信号,正好分别落在两种方式的盲区里。
1. 探测点的网络不等于用户的网络
合成探测只能说明“从探测点出发是通的”。以 cdnping 的 API 状态页 为例:它从 Cloudflare 边缘节点每 5 分钟探测一次各家官方 API 入口。2026-10-17 生成的数据快照里,探测来自 DEN 节点(丹佛),窗口是当天 UTC 08:40 到 10:05,共 242 次探测。在这个窗口里:
- Claude API 的中位耗时为 73 ms,xAI API 为 42 ms;
- DeepSeek API 为 353 ms,阿里云百炼为 615 ms,硅基流动为 631 ms。
这组数字只说明“从丹佛的边缘节点看过去”是这样,不能理解为国内服务比海外服务慢。从国内网络发起探测,结果通常会反过来。这正是合成监控的局限:数据准确,但代表性取决于探测点放在哪里。要知道用户那一侧的情况,需要在用户的网络里测,例如 cdnping 首页的在线测速,它在浏览器里测“首次连接”(含 DNS 与 TLS 握手)和“往返”(连接复用后 3 次请求的中位数)。两段耗时怎么拆分,可以参考 AI API 延迟拆解:连接建立、网关往返与首 token 时间。
2. 流式响应:200 不代表体验好
LLM 接口大多用 SSE 流式返回。一个常见情况是:响应头很快回来,状态码 200,但第一个 token 过了很久才到。只检查状态码的拨测会判定“正常”,用户看到的却是界面卡住不动。
所以 AI 应用的监控至少要分开记录:连接建立耗时、首 token 时间(TTFT)、完整响应耗时。合成探测可以用一个固定的小请求测 TTFT,得到可比的基线;RUM 则能告诉你真实的长提示词、长输出下 TTFT 分布是什么样。
3. “变笨了”:两种方式都很难直接测出来
模型输出质量下降,网络层面完全看不出来。可能的原因包括推理供应商换了、量化精度变了(例如 fp8、fp4),相关背景见 模型变笨还是换了供应商。cdnping 的 模型监测页 会记录 OpenRouter 上各供应商的量化精度变化,可以作为一条旁证。
在监控体系里,这类问题通常靠两种手段:
- 合成侧:准备几条答案确定的固定提示词(例如让模型按固定格式输出一段 JSON),定时调用并校验结构是否合法、关键字段是否正确。这比只看状态码多走了一步,但也只能覆盖你预设的题目。
- 用户侧:在界面上放一个“答得不好”的反馈入口,统计反馈比例的变化。
cdnping 的 挂了吗 页面就是这个思路:除了官方状态页和每 5 分钟的 API 探测,还收集用户一键报告,报告类型分为打不开、很慢、变笨了、限流、额度被重置、被登出。“打不开”“很慢”网络探测能部分发现;“变笨了”“额度被重置”“被登出”这类问题,探测基本测不到,只能靠用户告诉你。
没有流量时,真实用户数据会是什么样?
RUM 的盲区可以用一份公开数据直接看到。AGI Plan 公开了按真实请求计算的可用率数据(AGI Plan 是 cdnping 的商业合作方),接口是 https://agiplan.dev/api/status/requests?month=YYYY-MM,按小时给出成功和失败计数,只有 5xx 算平台失败。
快照中 2026-10-15 到 10-17 的 48 个小时桶里,能看到两种情况:
- 故障时信号很清楚:10 月 16 日 UTC 早上连续两个小时桶,分别是 664 成功 / 722 失败、455 成功 / 877 失败。真实请求失败率在这样的时段非常醒目,不需要额外探测就能发现。
- 低峰期几乎没有样本:10 月 16 日 UTC 20 点到 22 点的三个小时桶,每个只有 2 个请求。这时候即使服务完全不可用,RUM 也很难给出可信的结论,2 个请求失败 1 个就是 50%。
这就是需要合成探测补位的原因:固定频率的探测在低峰期依然有稳定样本。反过来,探测只覆盖固定请求,不能代替真实请求的失败率。可用率的口径和错误预算怎么算,可以看 SLI、SLO、SLA 到底差在哪。
按场景怎么选?一张判断表
| 你想回答的问题 | 首选 | 补充 |
|---|---|---|
| 上游模型 API 现在通不通 | 合成探测(多节点) | 官方状态页 |
| 半夜没人用时服务是否正常 | 合成探测 | — |
| 某个地区或运营商的用户是否变慢 | RUM(按地区分组) | 在该地区补一个探测点 |
| 流式响应首 token 是否变慢 | 两者都要:探测看基线,RUM 看分布 | — |
| 升级 SDK 或换网关后是否变差 | 合成探测做前后对比 | 灰度期间看 RUM |
| 模型输出质量是否下降 | 固定提示词的结构校验 | 用户反馈比例 |
| 是否触发了限流(429) | RUM(真实并发才会触发) | — |
| 判断是服务故障还是用户自己的网络 | 合成探测 + 用户侧测速对照 | 见 三步判断是服务故障还是网络问题 |
最小实施方案
下面是一个小团队可以照着做的组合,按顺序推进。
第一步:给每个上游入口加一个合成探测
最简单的版本用 curl,每 5 分钟跑一次,把各阶段耗时记下来:
curl -o /dev/null -s --max-time 10 \
-w 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer} total=%{time_total}\n' \
https://api.anthropic.com/v1/models
不带密钥请求时返回 401 是正常的,说明 DNS、TCP、TLS 和对方网关都工作了;超时、000 或 5xx 才说明有问题。判断规则建议写成“连续 2 次失败才告警”,避免单次网络抖动造成误报。
如果要测 TTFT,再加一个带密钥的固定小请求,开启流式,记录收到第一个数据事件的时间。注意 curl 的 time_starttransfer 记录的是第一个响应字节,可能只是响应头,不一定是第一个 token,严格测 TTFT 需要在脚本里解析 SSE 事件。
探测节点至少放两个:一个靠近你的服务器,一个靠近主要用户。更完整的告警配置可以参考 AI 应用可用性监控怎么做,脚本和口径参考 自己测 AI API 的可用率。
第二步:在前端给流式请求加 RUM 埋点
在发起对话的地方包一层计时,把结果用 sendBeacon 上报:
async function chatWithTiming(url, body, onChunk) {
const t0 = performance.now();
let ttft = null, status = 0, error = null;
try {
const res = await fetch(url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(body),
});
status = res.status;
const reader = res.body.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
if (ttft === null) ttft = performance.now() - t0;
onChunk(value);
}
} catch (e) {
error = e.name;
} finally {
navigator.sendBeacon('/rum', JSON.stringify({
status,
error,
ttft_ms: ttft === null ? null : Math.round(ttft),
total_ms: Math.round(performance.now() - t0),
net: navigator.connection ? navigator.connection.effectiveType : null,
}));
}
}
服务端只需要把这些字段存下来,按小时汇总成功数、失败数和 TTFT 分位数。不要上报提示词和回答内容;如果需要地区维度,在服务端按 IP 粗粒度解析出地区后丢弃原始 IP。
第三步:放一个用户反馈入口
在每条回答旁边放“答得不好”和“很慢”两个按钮,记录时间和所用模型组。它的样本量远小于 RUM,但能覆盖前两步都测不到的质量问题。
第四步:把三路信号放在同一张图上
告警时按下面的顺序看:
- 合成探测失败、RUM 失败率也升高:上游或你自己的服务出了问题,查官方状态页,阅读方法见 怎么看 OpenAI 状态页和 Claude 状态页。
- 合成探测正常、RUM 失败率升高:多半是用户侧网络、某个地区线路,或者真实并发触发了限流,按错误码和地区分组看。
- 两者都正常、用户反馈增多:考虑质量问题,跑一遍固定提示词校验,看看模型或供应商是否有变化。
- 合成探测失败、RUM 正常:先怀疑探测节点自身的网络,或者探测的入口和用户实际用的入口不一样。
cdnping 自己是怎么组合的?
cdnping 本身就是三种信号的组合,可以当作一个参考样例:
- 边缘合成探测:/status 从 Cloudflare 边缘每 5 分钟探测各官方 API 入口,每 15 分钟同步部分官方状态页,保留 30 天历史;
- 用户侧主动测量:首页测速 在用户浏览器里测连接和往返耗时,结果可以匿名计入统计,不记录 IP。它介于合成和 RUM 之间:请求是合成的,但发生在用户真实的网络里;
- 用户信号:/down 的一键报告,补上“变笨了”“额度被重置”这类探测测不到的问题。
各项数据的采集方式和口径写在 数据方法 页。需要说明的是,cdnping 从 2026-10-17 开始采集,本文引用的探测和请求数据都只覆盖很短的窗口,用来说明方法,不代表任何服务的长期表现。
常见误区
- 只做拨测,就认为用户体验没问题。拨测只证明探测点能连上,用户在哪个网络、用什么设备,它不知道。
- 只看 RUM 的平均值。AI 请求的耗时和输出长度强相关,平均值会被长回答拉高。按 TTFT 和总耗时分开统计,并看 p95。
- 把 401、429 都当成故障。401 往往是密钥或配置问题,429 是限流,处理方式不同,不要和 5xx 合并成一个失败率。429 的处理见 API 返回 429 Too Many Requests 怎么办。
- 低峰期用 RUM 判断可用率。几个请求的样本算出来的百分比波动很大,这个时段应以合成探测为准。
小团队不需要一次全做。先按上面的第一、二步加上合成探测和流式请求埋点,网络不通、上游 5xx 和首 token 变慢这几类问题就能看到;质量问题再靠第三步的反馈入口补。