性能数据埋点的上报
性能数据埋点的上报我们其实需要考虑 3 个问题:
- 非阻塞上报
- 使用什么方式进行埋点上报
- 上报的时机
非阻塞上报
一般的,我们的性能埋点工具都是抽象成为一个 sdk 的方式,这样能方便多项目的接入
这个 SDK 一般是需要初始化的,我们在初始化的阶段可能会进行如下的操作:
- 身份校验初始化,获取身份标识(如果是付费的性能监控 SDK 的话)
- 公参的初始化
- ...
总之,我们需要在初始化之后我们的埋点才能够正常发送出去,而很有可能得情况是,我们在初始化就需要进行埋点的调用(比如发送一个 pv 点),写成伪代码的方式如下:
const PerformaceOulaeSdk = function () {};
PerformaceOulaeSdk.prototype.init = function () {
// 假设 init 的执行时间需要 3 s
};
PerformaceOulaeSdk.prototype.log = function (...params) {
console.log('发起了埋点');
};
const sdk = new PerformaceOulaeSdk();
// 初始化完成之后,我们需要马上发送一个 pv 点
sdk.log('pv');
所以我们需要一个机制来保证:在 sdk 初始化成功之后,我们的埋点工具能够依次正常的发送所有埋点
而解决方案就是 promise 队列: 9_埋点的非阻塞上报
const PerformaceOulaeSdk = function () {
this.promiseCache = Promise.resolve();
};
PerformaceOulaeSdk.prototype.init = function () {
let _resolve = undefined;
let _reject = undefined;
this.promiseCache = new Promise((resolve, reject) => {
_resolve = resolve;
_reject = reject;
});
// 这里假设我们的初始化流程是非常慢的,最起码需要 3 秒钟
setTimeout(() => {
_resolve();
}, 3000);
};
PerformaceOulaeSdk.prototype.log = function (...params) {
this.promiseCache.then(() => {
// 调用埋点
});
};
埋点上报的方式
一般的埋点上报的方式有 3 种:
- XMLHttpRequest
- Image 对象
- sendBeacon
性能排序: sendBeacon > Image 对象 > XMLHttpRequest
XMLHttpRequest
这个无需多说,就是普通的 ajax 请求
Image
核心代码:
const imgLog = (...params) => {
const img = new Image();
// 这个是知乎的埋点地址
img.src = 'https://sugar.zhihu.com/glory_adreaper/glory_log?name=oulae';
};
使用 img 标签的优势:
- 没有跨域限制,埋点场景不需要考虑跨域的问题
使用 Image 对象进行埋点时,为什么请求的 url 通常是一张 1*1 的 GIF 图呢?
- 1*1 像素的图片中,gif 格式的图片是最小的
- 如果 response 返回 204,会触发 img 的 onerror 事件,并抛出一个全局错误;如果返回 200 和一个空对象则会有一个 CORB 警告
事实上,某些工具其实也会采用返回一个空对象的处理方式
sendBeacon
定义: sendBeacon 是一种浏览器定义的用来发送数据统计的 API,它存在着如下的优势:
- 能够保证数据发送的可靠性(页面卸载的场景下能保证请求发送成功)
- 数据的异步传输(可以理解为请求的发送会在浏览器资源空闲时去做)
- 不影响下一导航的载入(不阻塞页面刷新和跳转)
# 开启 express log 数据埋点服务
$ node app.js
# 开启页面服务
$ http-server
运行效果如下:
![]()
sendBeacon 请求是不受跨域影响的
虽然 sendBeacon 的性能是最好的,不过它的具体优势还是体现在页面卸载期间的埋点,因此当前市场上大部分的埋点工具使用的还是 Image 对象的解决方案
上报的时机
关于上报的时机,这里笔者认为:直接财报/使用宏任务上报即可,因为接触过的大部分工具其实都是使用的这种即时上报的方式
当然,这里如果要考虑到不阻塞页面网络进程的话,确实也是可以的,思路大概是:维持一个上报的消息队列,然后保证在页面卸载之前将所有的信息发出