在上一篇《网站数据统计分析之一:日志收集原理及其实现》中,咱们详细的介绍了整个日志采集的原理与流程。但是不是这样在真实的业务环境中就万事大吉了呢?事实往往并非如此。比如针对前端采集日志,业务的同学经常会有疑问:你们的数据怎么和后端日志对不上呢?后端比你们多了 N%!技术的同学也会问:你们怎么不打后端记日志呢?后端比你们效率和准确性更高。带着这些疑问今天咱们就来聊聊前端日志采集中的这些是是非非。
1、前端 VS 后端到底哪个准?该用谁?
这应该算是统计分析同学最为关注的问题之一了,到底哪个准我们应该从技术和业务两个角度来看待这个问题。
1.1 从技术架构层面日志分类
日志采集从技术架构层面而言就两种,前端与后端。前端日志采集说白了也就是页面部署统计代码,通过
<img src='https://my.oschina.net/log_xxx.gif?k=v'> 或者 javascript 发送 ajax 请求的方式来发送日志请求。后端一般在 webCGI 中通过日志 API 接口输出日志(比如 java 中 log4j),或者直接 webServer 中打印日志(比如 Tomcat)。那这两种技术方案各有何优劣呢?
1.1.1 前端 JS 采集
优势:轻量,调试友好,可扩展性维护性好
劣势:数据不安全,易丢失,客户端环境复杂兼容成本高
1.1.2 后端服务采集
优势:数据完整性有保证,业务数据安全
劣势:对后端业务代码有一定侵入性,容易受爬虫影响,非后台交互行为日志采集不到
通过上面比较我们可以看到前后端采集方案各有优劣,仅从数据量角度而言,后端日志采集方案能保证日志更为完整准确。
1.2 从业务架构层面日志分类
从业务架构出发,日志主要分为三大类:
行为日志:浏览、点击、各种交互行为等
行为日志一般侧重于用户各种行为交互、用户属性采集,用来评估用户体验、运营效果或者最后数据挖掘。比如漏斗、留存分析。
业务日志:用户、帖子、订单、库存等
业务日志往往和后端数据库、应用服务强关联,并且往往对日志有特别高的安全、性能、稳定、准确性要求,比如计费、支付等。
系统日志
这类日志一般用来衡量监控系统健康状况,比如磁盘、带宽是否满了,机器负载是否很高,或者RD自己通过程序输出的应用日志,用来监控应用服务是否异常,比如接口是否有超时,是否有恶意访问等。
1.3 前后端差异的原因
1.3.1 记录日志时机不同
对于行为日志而言,前端 js 采集脚本为了不影响主体业务逻辑以及取得相应业务参数,一般放在页面底部。假设咱们某个页面200个请求,后端日志会在某个请求返回给客户端之前就记录日志,而前端日志此时就比较吃亏了,需要等到浏览器执行完200个请求到页面底部 js 时,才能发出请求,这当中的时间差是日志差距的主要原因之一。那么问题就来了,如果一个页面用户打开后没加载执行完(因为前置js错误、性能延时、主动关闭等),应该算一个 pv/uv 吗?这种场景下,一般是认为不应该算的,很显然后端“抢跑”了,而且会比前端多不少。这个差异和你的页面复杂程度,用户网络质量密切相关,就实测数据来看,页面顶部到页面底部会有 10%左右差异,前端与后端会有20%以上差异。
1.3.2 爬虫影响
这个和公司的业务密切相关,一般都会有竞品或者其它商业、科研目的的爬虫抓取网站信息,低级的爬虫不会触发 js 请求,但会记录服务器日志,高级的爬虫封装了浏览器内核的才会执行 js 代码,这也是前后端日志差异的重要原因之一。
1.3.3 网络质量的原因
在移动端前端 js 请求丢失率更高,因为网络状况非常复杂,2G、3G、4G、WiFi 等等,请求从客户端发出来,由于不稳定的网络条件,不一定能到前端JS日志服务器。
1.3.4 平台差异
M端部分浏览器默认是单标签的浏览方式,任何一个点击、上一页、下一页按钮都会导致下一个页面会覆盖上一个页面,进而导致后端有日志但是前端无法记录到用户日志的情况。
另一种情况是可能部分老的移动端浏览器甚至都不支持 js,这就完全丢失了这部分日志。
1.3.5 其它差异
缓存、以及其它的用户行为也可能导致请求执行到了,但是没有发送成功,比如用户在页面加载完成后,请求还未发送完成时关掉页面,可能导致请求被 cancel 掉,这对一些用户黏性不是很强,跳出率很高的网站而言是另一个差异来源。
1.4 究竟该用谁?
综上所述,到底哪个准其实是相对而言的,得分业务场景来看,不可能有一个绝对值,至于用哪个就看你具体的业务诉求了。
前端 JS 日志只适合用来做全流量分析与统计,更多的是用来反应整体的流量趋势与用户行为,并不能精确到单个的用户行为与单次的访问轨迹。它的优势在于与后端解耦,调试、扩展、采集方便,额外的开发成本很低,适合做成 SAAS 模式。这也是为什么百度统计、GA它们采用这套方案能做成一款行业通用,甚至全球化的数据产品。
如果对日志有特别高要求的业务场景比如计费、支付等等,要求日志一条不丢同时日志安全稳定,那就必须依赖数据库或者后端日志,但相应的开发维护成本会大些。
2、GA、百度统计、自己的日志,到底哪个准?
记得几年前流行过一句经典语录:幸福是个比较级,要有东西垫底才感觉的到。言外之意也就是说凡事经过比较后,才能区分出优劣。回到咱们的话题,早期创业公司一般会选择第三方统计系统,一来成本低,二来投资人往往需要看第三方数据对你公司的业务运营状态作出评估或者估值。但第三方统计只能做通用统计,对个性化的统计与深度数据挖掘无能为力,而且企业的商业机密堪忧。因此业务做大后公司往往会选择自己搭建数据平台和日志采集系统。那么问题马上接踵而至:自己的日志怎么和 GA、百度统计对不上呢?甚至这几者中任意两个都存在一定差距。
其实原因大抵都是1.3中提到的原因,除此之外还有比较细节的技术实现差异,比如请求大小,域名是否被屏蔽(比如去年 5月开始 GA 就被墙了)、第三方 Cookie、埋码是否完全、统计口径与规则等等。
当你发现其中存在差异时,需要验证各种可能原因去校验数据,如无特殊原因,最终应该以自己采集的为准。
3、数据丢了吗?丢多少?
在 1.3.1 中提到了一些内部线上测试数据,外界也有一些同学做过类似的测试,结论都差不多,部分业务场景下丢失率高到不可思议。比如点击前发送日志然后立即跳转,如果不做任何优化处理,这种场景的丢失率巨高,往往超过 50%。
4、前端日志采集丢失问题能解决吗?
4.1 传统解决方案
从技术角度可以归纳为两点:
用户关闭页面过早,统计脚本还未加载/初始化完成
用户关闭或者跳出页面的时候,请求未发出
针对第一点,概率较小,一般的处理方式就是,不要把统计脚本参合到其他脚本中,单独加载,并且放在前头,让它优先加载。很多公司的做法是,不让开发者关心统计脚本的加载,用户请求页面的时候,Nginx 会在 Body 开始标签位置注入一段脚本。或者把 js 动态请求换成 <img src> 硬编码的方式发送请求。
对于问题二,处理方案就有很多了。
4.1.1 阻塞式的 Ajax 请求
还记得 XMLHttpRequest::open 方法的第三个参数吧,如果设置为 false 就是同步加载,
window.addEventListener('unload', function(event) {
var xhr = new XMLHttpRequest(),
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
xhr.open('post', '/log', false); // 同步请求
xhr.send(data);
});
阻塞页面关闭,当然可以在 readState 为 2 的时候就 abort 请求,因为我们不关心响应的内容,只要请求发出去就行了。
4.1.2 暴力的死循环
原理跟上面类似,只不过是使用一个空的死循环阻塞页面关闭,
window.addEventListener('unload', function(event) {
send(data);
var now = +new Date;
while(new Date - now >= 10) {} // 阻塞 10ms
});
4.1.3 发一个图片请求阻塞
大部分浏览器都会等待图片的加载,趁这个机会把统计数据发送出去
window.addEventListener('unload', function(event) {
send(data);
(new Image).src = 'http://example.com/s.gif';
});
以上提到的几个方案都是一个原理,让浏览器继续保持阻塞状态,等数据发送出去后再跳转,这里存在的问题是:
少量浏览器下可能不奏效
等待一会儿再跳转,用户体验上打了折扣,尤其是移动端上
是否有更好的方案解决这个问题呢,前端同学秉着「小强精神」也提出了两个可实践的方案。
4.2 优化方案
不就是埋点统计数据嘛,非得在当前页面发送出去?优化方案的思路具有一定的跳跃性,我们考虑将数据在下跳页中发送,那么问题就转换为,如何将数据传递给下跳页?
对于链接点击量的统计,我们可以将链接信息通过 url 传递给下跳页,传递思路如下:
4.2.1 url 传参
通过数组标识一个链接的位置信息,如 [站点id,页面id,模块id,链接index],通过四个参数可以惟一标识链接位置属性,使用 URL param 参数将数组数据传递给下跳页,等待由下跳页将数据发送出去。
这里存在的问题是,下跳页中必须部署同样的统计脚本,但对一个系统来说,这是很容易做到的。我们也不会在自己的网页上放其他网站的链接吧,所以整个数据的统计都在一个闭环内。还有就是如果统计业务复杂的情况下,这种方案的维护成本与用户体验很差。
4.2.2 通过 window.name 传递数据
window.name 是浏览器给我们开放的一个接口,设置该属性的值后,即便页面发生了跳转,这个值依然不会变化,并且可以跨域使用。
这里存在的问题是,该属性可能被开发者用于其他途径。我们可以限制开发者直接使用 window.name,封装接口,通过接口调用,如 aralejs 提供的 nameStorage,
nameStorage.setItem(key, value);
nameStorage.getItem(key);
nameStorage.removeItem(key);
储存形式为:
scheme nameStorage datas
| |
------------ ------------------------
nameStorage:origin-name?key1=value1&key2=value2
-----------
|
window origin name
以上虽然基本解决了数据丢失和体验差的问题,但是这也很大程度依赖于开发者的编程习惯,如不能随便玩耍 window.name ,业务复杂的场景下容易出问题,比如容易被覆盖;也对系统有一定的要求,必须在所有页面上部署同样的埋点脚本。
4.2.3 localstorage 存储重发
localstorage 是 HTML5 提供的两种在客户端存储数据的新方法之一,对于丢失率高的场景,咱们可以先把请求日志存储在 localstorage 中,失败后在下个页面重发,并且可以添加重试机制,这样日志的完整性能很大程度上提高。从性能角度讲还可以统一发送,减少连接。
但是针对跳出率高的场景,这种方式实测效果并不明显。
4.3 这件事情应该交给浏览器来解决
上面提到的各种方案,不乏黑科技,然而存在的问题还是一大堆,如果团队的开发者执行力不够,中途容易出现各种麻烦。所以真正能够解决这个问题的,必然还是浏览器本身!
为什么不能给用户提供这样一个 API,即使页面跳转了,也能够将上个页面的请求发出去呢?庆幸的是,W3C 工作组也想到了这个问题,提出了 Beacon API 的 草案。
Beacon API 允许开发者发送少量错误分析和上报的信息,它的特点很明显:
在空闲的时候异步发送统计,不影响页面诸如 JS、CSS Animation 等执行
即使页面在 unload 状态下,也会异步发送统计,不影响页面过渡/跳转到下跳页
能够被客户端优化发送,尤其在 Mobile 环境下,可以将 Beacon 请求合并到其他请求上,一同处理
sendBeacon 函数挂在在 navigator 上,在 unload 之前,这个函数一定是被初始化了的。其使用方式为:
window.addEventListener('unload', function(event) {
navigator.sendBeacon('/collector', data);
});
navigator.sendBeacon(url, data);,第一个参数为数据上报的地址,第二个参数为要发送的数据,支持的数据格式有:ArrayBufferView, Blob, DOMString, 和 FormData。
Beacon 的还有一个非常实用的移动端使用场景,当用户从浏览器切换到其他 app 界面或者 Home 屏的时候,部分浏览器默认会停止页面脚本的执行,如果在这个时候使用了 unload 事件,可能会让你失望,因为 unload 事件并不会触发,此时,Beacon 就派上用途了,它是不会受影响的。
本节是对页面打点丢失问题的简单探讨,枚举了我们通常会用到的一些解决方案,可能不是很完善,如果你有更好的建议,可以提出来。
很多问题,我们绞尽脑汁,可能很少会考虑,这个问题是不是应该由我们来解决,或者说这个问题交给谁处理是最恰当的。本文的探讨可以看到,浏览器本身才是最好的问题解决方,当网站流量变大之后,上面提到的丢失问题就更加明显,这也迫使浏览器本身做了改善,自然也在情理之中。
5、总结
至此,本文探讨了前端日志采集过程中的一些常见问题,解释了数据分析以及RD同学一些常见困惑,并提供了一些相应的优化思路与方案。文中提到的各种问题还是以 PV 为主,其实还存在另一个常见的指标差异:UV,这个指标的差异原因更为复杂,改天有空再详细分享下。总之日志采集与统计分析没有部分同学想象的那么简单,这里面的坑其实很多,需要大家不断的去探索,从技术和业务角度去不断优化改进,前路漫漫。
说明:本文第四节结合了 Refer [1] 以及本文作者的实际经验整理而成,在此致谢,感谢分享。
6、Refer:
[1] 页面跳转时,统计数据丢失问题探讨
http://www.barretlee.com/blog/2016/02/20/navigator-beacon-api/
[2] 网站数据统计分析之一:日志收集原理及其实现
http://my.oschina.net/leejun2005/blog/292709
[3] 站长统计、百度统计、腾讯统计、Google Analytics 哪一统计的数据相对准确些?
https://www.zhihu.com/question/19955915
[4] 网站分析——我们的数据准确吗?
http://bit.ly/1RZnvWi
[5] 为什么两个监测工具报告中的数据不同
http://bit.ly/1QebUBe
[6] JavaScript API 调用说明
http://help.dplus.cnzz.com/?p=62#dplus.track
[7] 数据采集与埋点
http://www.sensorsdata.cn/blog/shu-ju-jie-ru-yu-mai-dian/
[8] Beforeunload打点丢失原因分析及解决方案:
http://blogread.cn/it/article/6804?f=wb
[9] beforeunload丢失率统计:
http://ued.taobao.org/blog/2012/11/beforeunload%E4%B8%A2%E5%A4%B1%E7%8E%87%E7%BB%9F%E8%AE%A1/
[10] 互联网数据分析的底层应用架构
https://zhuanlan.zhihu.com/p/24018306
[11] 数据处理中的准确性问题
http://bit.ly/2vkImzb