IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Beforeunload打点丢失原因分析及解决方案

阿里巴巴(中国站)用户体验设计部博客 2014-03-19 22:42:44 累计浏览 3,830 次
本机暂存

作者-1688搜索前端朱铁根,1688数据仓库胡大军

   淘宝的鱼相在2012年8月份发表了一篇文章,里面讲述了他们通过一个月的数据采集试验,得到的结果是:如果在浏览器的本页面刷新之前发送打点请求,各浏览器都有不同程度的点击丢失情况,具体点击丢失率统计大家请看下图(数据日期为2012年7月份):

   alt

   从图中可以看出,chrome,safari这类webkit内核的浏览器在本页刷新之前发送打点,导致的丢失最为严重,分别为61%,76%,而ie8丢失的情况最少,为7%。 (具体大家可以参看此文:http://ued.taobao.com/blog/?p=116 )

原因分析:

   基于这种现象的产生,抛开打点服务器的处理性能和网络网速因素,我们从前端的角度来分析一下:

   当点击浏览器的当前页面某个元素后,当前页面创建image对象,并把这个image对象的src设为指向打点服务器的url,发送打点请求。接着,当前页又向页面服务器发送页面跳转请求。假设页面服务器很快就返回了请求的html, 则浏览器窗口接收到页面服务器的响应后,开始卸载保存在用户内存中的跟当前页面的有关的所有元素,包括刚才创建的用来向打点服务器发送打点的image对象。假设这时打点服务器对这个image发送的打点请求还在处理阶段,比如正在向数据库插入数据,还没有做response响应。这时,由于页面卸载了,image对象被销毁了,该对象发送的src请求也就被终止了。导致这个打点失败了。类似于垃圾回收机制的影响。如果刚创建的image元素立即被内存垃圾回收了,而这个图片的HTTP请求尚未建立,那么在被回收时这个请求就会被取消,导致打点并没有真正发出。一个http请求的成功,必须是与后端服务器握手的成功。根据DW的胡大军检测到的数据,后端服务器监测到3次tcp协议的请求后,http请求就被终止了。从这种现象上说明,浏览器在页面跳转之前确实发送了打点请求,由于页面跳转过快,发送打点的image对象因页面卸载而销毁了,请求也就被终止了。

   具体示意图如下:alt

   搜索端应用searchweb曾经做过试验,如果searchweb端响应延迟100ms后返回html数据,这种在页面卸载之前发送的请求打点(我们这里称它为Beforeunload 打点)的回收率就明显上升,差不多得到了100%的回收率。页面服务器响应时间越长,Beforeunload 打点丢失率越低。因为页面服务器响应时间变长后,打点服务器有足够的时间处理打点请求, 然后返回给image对象做response响应。在本页面卸载刷新之前,image对象的src请求已经成功得到了响应。这时再销毁该对象,对打点丢失已经没什么影响了。可以说,只要用来打点的image对象在打点成功之前没有被销毁,打点就不会丢失。如果链接跳转的方式是新窗口打开的,也就不会发生打点丢失的情况。因为当前页创建的打点image对象被没有被销毁。

解决方案

   找到了Beforeunload 打点丢失的原因,我们再来思考一下解决方案:

   因为页面创建的image对象在打点请求还没响应之前就被卸载了。如果单纯提升打点服务器性能,确实能加快image打点请求的响应返回,减少Beforeunload打点丢失情况。问题是,打点服务器要提升多少性能才是合适的?其实是页面服务器(应用服务器)跟打点服务器的一场竞赛。如果页面服务器性能大大提升了,而打点服务器没有提升,那么差距又拉大了。 又或者像searchweb做的试验那样,页面服务器(应用服务器)做延迟响应处理,这样Beforeunload打点丢失就能得到有效遏制。问题是这其实是对用户体验的极大伤害。现如今大家都在拼命提升应用页面服务器的性能,让页面能加载的更快,用户使用网页感觉更流畅。为了能在服务后端打上点,我们却要自己的应用延迟响应,跟我们提升用户体验的目标背道而驰。要知道,任何软件的应用,良好的用户体验是第一位的。所以,此路不通也。

   那有没有一种更好的办法?比如在页面刷新之前缓存打点数据,页面卸载刷新后,把原来页面刷新之前保存的打点数据取出来,再打点出去?这样,刷新后的页面打点跟点击pv基本上就是1:1了,因为pv的计算也是根据跳转后的页面url来统计的。答案是:还真有。

   在浏览器页面被卸载的过程中,除了window.name属性对象外,其他对象元素都被销毁了。所以我们在页面卸载之前把请求打点的数据保存在window.name中。然后,页面刷新后,再取出这个window.name对象中保存的打点数据,发送打点请求。

   如果单存要存储打点数据,让该数据在页面刷新之后仍然能够被访问,有cookie对象,高级浏览器还支持localStorage,为什么只推荐window.name ? 原因如下:

  • window.name没有类似于cookie,localStorage的跨域问题。如果从www.taobao.com跳到www.tmall.com,天猫的页面仍然能够得到在淘宝页面设置的window.name的值。而cookie只能跨主域。Localstorage则不能跨域,连跨主域都不行,所以,在上面的场景中,如果使用cookie或者localstorage,天猫页面无法获取淘宝页面设置的cookie或者localstorage;

  • Window.name所有浏览器都支持,无论是webkit内核还是IE系列,所有浏览器都一概支持,而且表现一致。

  • 页面刷新打点完毕后,原来被保存的打点数据已经没有用了,数据保存形式应该是临时性质的。cookie,localstorage对象的设计是用来存储持久数据的,而window.name的设计,就是为保存页面临时数据的。如果页面窗口关掉了,内存回收了widow对象,window.name也就不存在了。两者的需求与用途一致。

  •    如此看来,用window.name来存储Beforeunload 打点请求的数据确实是个不错的主意,但是还有一个问题:如果window.name属性对象已经被应用程序使用了,也就是window.name已经被应用设为某个值了,这时我们再修改这个window.name,岂不是对应用程序造成了破坏?

       答案是:没有关系。我们在页面跳转前,拼接完打点串后,可以这样写

window.name=window.name+”|%”+logURL;

   其中,logURL是要打点的请求串。通过这样设置window.name,原来应用程序的window.name的数据仍然被保存着。在页面跳转后的打点js中,可以这样写打点代码:

  var windowUrl=$.trim(window.name);
  if(windowUrl && windowUrl.indexOf("|%")!==-1){
    var urlArray=windowUrl.split("|%"),orginName=urlArray[0];
    windowUrl=urlArray[1];  
  //把window.name归还给应用程序
  window.name=orginName;        
    //发送打点请求
  sendByImg(windowUrl);
}

   上面这段页面曝光打点的代码,放在标签里面,类似于1688中文站的beacon.js,专门用来打点。因为打点代码在head标签里,一般在所有应用代码之前执行。(一般应用代码在domready的时候执行)所以在该代码执行完后,window.name已经被设置为原来应用的值了。这样对应用造成的破坏也就不存在了。

   解决方案的示意图如下:

   alt

解决方案数据验证效果

   蓝色为Beforeunload 打点,红色为用window.name先存储打点数据,页面刷新跳转后,再从window.name取回打点数据,曝光打出的点。 Chrome浏览器下的平均数据如下:

   alt

   alt

   可以看到,通过window.name的解决方式,打点效果提升了13%。

   IE平台下的总平均回收效果如图(包括ie6,ie7,ie8,ie9,ie10总和):

   alt

   alt

   IE系列各浏览器的平均回收效果如图:

   alt

   (注:目前中文站的用到的fdev-min.js 依赖的jQuery1.7.2的attr方法有个bug,当IE10浏览器的ie7文档模式下调用attr方法时,程序会抛异常,导致了window.name不能被赋值。上图中,IE10有可能因为受此影响,表现不如Beforeunload 打点。关于这个jQuery bug的详细介绍,有兴趣的同学,可以看看这篇jQuery官网的bug帖 http://bugs.jquery.com/ticket/12577)

   alt

   alt

方案总结

   总体而言,新方案跟无痕打点的Beforeunload 打点方式时相比,在ie上略有提升,chrome浏览器上,提升最为明显,相比原来提升了13%。这种方案还是很有效果的。

同分类推荐文章

  1. translateZ() (2026-06-25 21:18:56)
  2. translateY() (2026-06-25 21:17:56)
  3. translateX() (2026-06-25 21:16:01)

查看更多 前端 文章 →

建议继续学习

  1. JQuery实现Excel表格呈现 (累计阅读 48,352)
  2. 深入理解Javascript之执行上下文(Execution Context) (累计阅读 18,411)
  3. 浅析http协议、cookies和session机制、浏览器缓存 (累计阅读 17,452)
  4. 从输入 URL 到页面加载完成的过程中都发生了什么事情? (累计阅读 15,938)
  5. libcurl的使用总结(二) (累计阅读 15,088)
  6. 图片动态局部毛玻璃模糊效果的实现 (累计阅读 14,849)
  7. 天朝第二代身份证号码的验证机制 (累计阅读 14,764)
  8. HTML 5 的data-* 自定义属性 (累计阅读 14,353)
  9. 分享一个JQUERY颜色选择插件 (累计阅读 14,225)
  10. 什么是全栈工程师? (累计阅读 14,041)