如果只看边缘节点的缓存命中率,你很难解释为什么用户还在反馈页面打不开。传统CDN监控习惯围绕带宽、连接数、缓存命中率展开,这些指标描述的是平台运行状态,并不等于用户的真实体验。一次请求从DNS解析到完整内容呈现,中间要经过连接建立、首包等待、分块传输多个阶段,任何一个阶段劣化都可能让用户感觉卡顿甚至失败。因此,构建一套用户体验感知的质量监控系统,需要重新定义QoS指标的采集点、计算口径和告警方式。

一、先重新定义CDN的QoS视角
缓存命中率描述的是请求是否命中边缘缓存,但用户从发起请求到页面可用,中间还隔着DNS解析、TCP建连、TLS握手、首字节到达和完整内容传输多个环节。任何一个环节劣化都会反映到体验上,却不一定触发传统带宽或命中率告警。比如边缘节点命中率保持98%,看起来一切正常,但如果剩下2%的回源请求平均耗时超过3秒,那么命中这些回源请求的用户就会感受到明显卡顿。
所以第一件事是把监控目标从平台自身运行状态切换成用户请求生命周期。一次请求可以拆成连接阶段、首包阶段、传输阶段和关闭阶段。连接阶段关注DNS耗时、TCP握手耗时和TLS握手耗时;首包阶段关注TTFB;传输阶段关注下载速率、卡顿次数和完成率;关闭阶段关注连接复用和失败重试。这样拆分后,QoS指标不再是一个笼统的可用率,而是能定位到具体阶段的时间与成功率。
在这个视角下,可用性不能只统计状态码200的比例,还要看用户是否完成完整内容加载。比如一个视频文件前几秒正常播放,后续因为节点回源失败中断,日志里可能记录的是206状态码,对平台来说似乎还算正常,但用户已经感知到失败。把完成率纳入指标,可以暴露这类隐形故障。完成率通常定义为成功传输完预期字节数或达到播放时长的会话占比。
二、核心指标的分层设计与计算口径
指标体系可以分四层:可用性、延迟、吞吐和稳定性。可用性层包括请求成功率、完成率、有效回源率;延迟层包括DNS耗时、连接耗时、TTFB、首屏时间;吞吐层包括平均下载速率、峰值速率、慢速比;稳定性层包括卡顿率、重传率、连接复用率、丢包率。每一层都需要明确计算口径,否则不同数据源得到的结果无法对齐。例如日志中的请求耗时可能只包含边缘节点处理时间,而RUM中的首包时间则包含完整网络链路。
延迟类指标建议全部使用分位数而不是平均值。平均值会被极少数超长请求拉高,反而掩盖大多数用户的真实感受。以TTFB为例,P50、P95和P99需要同时展示。P95表示95%的用户首包时间不超过该值,适合作为SLO目标。例如静态资源P95 TTFB设置在300毫秒以内,动态接口P95设置在800毫秒以内,具体值要结合业务类型调整。如果只展示平均TTFB,一个500毫秒的常规请求和一个5秒的异常请求平均后可能得到900毫秒,容易让团队误判问题严重程度。
下面用一个Python脚本示例,展示如何从边缘访问日志中计算成功率和分位延迟。假设日志字段包含状态码和请求耗时,脚本过滤掉健康检查和爬虫请求后输出结果。
import json
from typing import List, Tuple
def load_logs(path: str) -> List[dict]:
logs = []
with open(path, 'r', encoding='utf-8') as f:
for line in f:
item = json.loads(line)
if item.get('user_agent', '').lower().find('bot') == -1:
logs.append(item)
return logs
def percentile(values: List[float], p: float) -> float:
if not values:
return 0.0
sorted_values = sorted(values)
k = (len(sorted_values) - 1) * p
f = int(k)
c = k - f
if f + 1 < len(sorted_values):
return sorted_values[f] * (1 - c) + sorted_values[f + 1] * c
return sorted_values[f]
def qos_report(logs: List[dict]) -> Tuple[float, float, float]:
total = len(logs)
success = sum(1 for log in logs if log['status'] < 400)
latency = [log['latency_ms'] for log in logs if log['latency_ms'] > 0]
return success / total if total else 0.0, percentile(latency, 0.95), percentile(latency, 0.99)
if __name__ == '__main__':
records = load_logs('access.log')
success_rate, p95, p99 = qos_report(records)
print(f'成功率={success_rate:.4f}, P95延迟={p95:.1f}ms, P99延迟={p99:.1f}ms')
卡顿率在视频和直播场景更重要。它不能简单用网络抖动来替代,需要结合播放器缓冲事件。通常定义为单位播放时长内出现二次缓冲的累计次数,或者缓冲时长占播放时长的比例。若卡顿率超过0.5%,就可以认为用户体验明显下降。下载场景则关注慢速比,即下载速率低于某个阈值的样本占比,阈值可以按文件大小和业务承诺带宽折算,例如一个5MB的文件承诺10秒内完成下载,那么慢速阈值可以设为500KB/s。
三、三类数据源如何组合成完整监控链路
只看边缘日志看不到用户终端在最后一公里的表现。日志、拨测和真实用户监控各有盲区,也各有不可替代的价值。边缘日志覆盖全量请求,适合计算成功率和回源指标,但缺少浏览器渲染时间和用户真实网络环境。拨测可以周期性模拟不同地域和运营商访问,适合监测骨干链路和节点可用性,但拨测节点分布再广也无法完全还原真实用户。RUM通过页面埋点采集真实用户数据,最接近体感,但样本受限于页面接入情况和用户设备性能。
比较合理的组合方式是:边缘日志作为全量兜底,拨测做7×24小时主动探测,RUM作为体验复核和专项分析。例如某个区域用户投诉视频卡顿,可以先看边缘日志中该区域节点的回源失败率和响应时间是否有异常;如果正常,再分析拨测中同区域同运营商的连通性和抖动;如果仍未定位,最后从RUM数据中提取具体用户会话,查看终端参数、网络类型和资源加载瀑布图。这种三层下钻可以避免一上来就被单点指标误导。
RUM采集通常使用浏览器性能接口。下面是一段前端JavaScript示例,利用PerformanceObserver监听资源加载耗时,并上报到采集端点。注意实际生产环境需要做采样和敏感信息过滤。
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.initiatorType === 'img' || entry.initiatorType === 'script') {
const data = {
name: entry.name,
duration: Math.round(entry.duration),
transferSize: entry.transferSize,
connectTime: Math.round(entry.connectEnd - entry.connectStart),
requestTime: Math.round(entry.responseStart - entry.requestStart),
ttfbs: Math.round(entry.responseStart - entry.startTime)
};
navigator.sendBeacon('/collect', new Blob([JSON.stringify(data)], {type: 'application/json'}));
}
}
});
observer.observe({entryTypes: ['resource']});
这段代码先观察资源加载条目,筛选出图片和脚本,然后把耗时分解为连接、请求和首包三个阶段,通过sendBeacon发送给采集端。需要注意的是,跨域资源如果没有设置Timing-Allow-Origin响应头,很多时间字段会显示为0,采集端要识别这种无效数据,否则计算出来的延迟会偏小。因此在接入RUM时需要同步推动资源服务器配置正确的CORS头,否则采集到的数据不能反映真实网络耗时。
四、阈值、告警与持续优化实践
指标体系建立之后最难的不是看数,而是设置合理的阈值。阈值过松,故障发现滞后;阈值过紧,告警风暴让值班人员麻木。建议先采集至少两个完整业务周期的数据,计算各指标的历史分位分布,再根据业务SLO设置告警阈值。例如成功率可以按分钟聚合,当连续三个分钟窗口低于99.5%时触发P2告警;首包时间按5分钟聚合,P95超过阈值时触发P3告警。告警等级要和影响范围挂钩,避免所有指标一刀切。
稳定性指标需要引入基线和环比。比如DNS耗时忽然从20毫秒升高到200毫秒,绝对值未必触发固定阈值,但环比变化可能已经说明某运营商递归解析异常。因此监控系统需要同时支持固定阈值和动态基线。动态基线可以用滑动窗口的中位数和标准差来检测突变,或者使用简单的指数移动平均。对延迟这类非正态分布指标,分位数检测比均值检测更有效,因为尾部样本往往更能反映用户遇到的极端情况。
最后要形成从监控到优化的闭环。每一次告警处理后,应该回溯相关链路,确认是缓存配置问题、回源超时、节点过载还是运营商路由异常,并把结论沉淀到指标口径或告警规则里。比如发现多次故障集中在某个源站响应慢,就可以增加源站健康检查频率,并将回源超时纳入核心告警。没有闭环的QoS指标体系只是看板,只有当它能推动节点调度、缓存策略和回源配置变化时,才算是真正帮助改善了用户体验。