视频业务里最让人头疼的问题之一,就是CDN侧统计的播放次数、流量消耗和前端真实情况对不上。很多团队把希望寄托在Beacon上报上,认为它能在页面关闭时安静地把数据发走,但实际运行中Beacon请求失败或丢失非常普遍,直接导致运营报表失真。要彻底解决这类问题,必须先理解Beacon的工作机制和它天生的局限性。

Beacon机制为何会在视频场景频繁丢失数据
Beacon接口设计的初衷,是让浏览器在用户离开页面时仍能异步发送少量数据,且不会阻塞主线程或推迟卸载。调用 navigator.sendBeacon 后,浏览器会把请求放进后台队列,由网络栈自行调度。问题在于,这种调度完全不受前端控制。在视频播放结束或跳走的瞬间,如果设备进入休眠、切到后台或网络抖动,后台队列里的请求可能根本没机会发出去就被系统清理。
另一个容易被忽视的点是跨域与体积限制。Beacon请求如果跨域且服务端没有正确返回 Access-Control-Allow-Origin,部分浏览器会直接丢弃。同时多数浏览器对单次Beacon的body大小有限制,超过后返回false且不发送。视频场景常要带分段码率、卡顿时长、IP地域等字段,很容易触碰上限。再加上公司网关或CDN节点对异常UA的过滤,统计丢失就成了常态。
从协议层看,Beacon本质是HTTP POST,但浏览器不保证到达率,也不提供发送结果回调。它和业务RPC不同,属于“尽力而为”的埋点通道。当我们将核心指标如UV、完播率只依赖这一条通道时,任何一环的静默失败都会放大成报表缺口。因此视频CDN统计不准,首先该怀疑的不是CDN算错,而是Beacon根本没送到。
对比常见兜底方案与各自适用边界
单纯依赖Beacon风险太高,工程上通常用多通道互补。最古老的是 Image 打点:拼一个带参数的GIF地址,利用浏览器加载图片天然跨页存活的特性上报。它的好处是兼容极强,连IE都能用;缺点是只能GET且长度受URL限制,无法携带复杂JSON,且在HTTPS页混用HTTP资源会被挡。
现代方案常用 fetch 配合 keepalive 标志作为Beacon替补。fetch的keepalive能在页面卸载后延续请求,且支持大body和POST。但它要求开发者自己处理重试和队列,不像Beacon那样由浏览器托管。下面是一段在视频暂停时优先Beacon、失败转fetch的示例:
function reportVideoStat(data) {
var body = JSON.stringify(data);
// 优先使用Beacon
if (navigator.sendBeacon) {
var blob = new Blob([body], {type: 'application/json'});
var ok = navigator.sendBeacon('https://stat.ipipp.com/v.gif', blob);
if (ok) return;
}
// Beacon不可用则fetch兜底
fetch('https://stat.ipipp.com/v.gif', {
method: 'POST',
body: body,
keepalive: true,
headers: {'Content-Type': 'application/json'}
}).catch(function(e) {
// 存入localStorage下次补发
localStorage.setItem('stat_cache', body);
});
}
除了前端通道,服务端也要做去重与补偿。因为多通道可能导致同一播放行为被重复计数,CDN统计侧应依据播放会话ID做幂等。若前端发现本地有缓存未发,可在下次进入播放页时补传,服务端再按时间窗合并。这种“前端尽力发、服务端防重复”的模型,比单靠Beacon稳健得多。
构建可校验的视频统计埋点体系
要解决上报丢失带来的不准确,不能只修管道,还要让数据可核对。推荐在视频播放器内部维护一份本地流水账:每次起播、切换清晰度、卡顿都写本地日志,包含时间戳与唯一事件ID。这样即使Beacon丢了,也能通过抽样回捞或用户反馈定位缺口规模。
具体落地时,可以用Web Worker接管上报队列,避免主线程卡顿影响卸载。Worker里维护指数退避重试,遇到 navigator.sendBeacon 返回false就转fetch并落盘。下面是一段Worker内简化逻辑:
// worker.js
self.onmessage = function(ev) {
var item = ev.data;
var sent = false;
if (self.navigator && self.navigator.sendBeacon) {
sent = self.navigator.sendBeacon(item.url, JSON.stringify(item.payload));
}
if (!sent) {
fetch(item.url, {method:'POST', body: JSON.stringify(item.payload), keepalive:true})
.catch(function() {
// 写回主线程缓存
self.postMessage({type:'fail', item:item});
});
}
};
最后,运营侧应建立“前端真实事件数”与“CDN回收数”的偏差告警。当偏差连续超过阈值,比如大于5%,就自动触发埋点脚本健康检查。只有把Beacon丢失当作常态去设计系统,而不是当作意外去修补,视频CDN统计才能真正可信。实践中结合本地日志、多通道发送与服务端去重,可将丢失率压到千分之一以下,足以支撑业务决策。