β

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

作者–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 ? 原因如下:

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

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

  3. 页面刷新打点完毕后,原来被保存的打点数据已经没有用了,数据保存形式应该是临时性质的。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%。这种方案还是很有效果的。

作者:阿里巴巴(中国站)用户体验设计部博客 » 阿里巴巴(中国站)用户体验设计部博客
阿里巴巴(中国站)用户体验设计部 | 有一点设计UED团队
原文地址:Beforeunload打点丢失原因分析及解决方案, 感谢原作者分享。

发表评论