日志采集与用户行为链路跟踪

时间:2022-06-06 19:47:46

日志采集这部分内容,其实在上一篇文章 阿里巴巴大数据实践-读书笔记 里面多多少少已经提到了一些。不过正如前文提到的,这部分内容,从技术的角度来说,未必有多么高深,但是从业务角度来说,要做到完善却也很难,特别是在分析用户行为链路的场景下,所以这篇专门来讨论一下这一块的内容。

所谓用户行为,就是用户在你的网站或者APP上所做的动作,比如:搜索内容,浏览页面,观看视频,购买商品,收藏,评论等等。

那为什么要采集和记录用户的行为呢?吃饱了没事干,窥探用户的隐私么?

日志采集与用户行为链路跟踪


当然不是,说好听点,是为了提高产品服务质量,提供个性化服务,说直接点呢,本质上还是为了更好的赚钱呗。在流量换金钱的互联网商业模式下,尤其是下半场阶段,流量的价值,无庸置疑,来之不易的流量,当然需要珍惜了。所以搞清楚你的用户在自己的网站/APP上到底都做了什么,想要做什么,可以被"拐骗"去做些什么,也就至关重要了:)

要搞清楚这些,当然要有数据,所以,你需要采集和分析用户的行为,而行为日志,无疑是最主要的数据来源。

记个日志有什么难的?

要想把用户行为分析得彻底,那就需要全方位的采集数据,“全方位”这几个字,实际上就是在类似我司这种业务链路复杂的电商环境下的系统中,日志采集最大的难点所在了。

全方位,意味着不能遗漏。这实际上又包括了几层涵义:

首先,页面全覆盖。类似我司这类业务场景的系统,用户交互界面繁多,用户交互的行为也是多种多样。商品搜索,图墙浏览,资讯分享,收藏,加购,下单,支付,评论。。。而这些行为的载体也是多种多样,PC端,移动端;H5,native;IOS,安卓;应用内打开,微信小程序内打开等等。

每个业务,每种载体,每个渠道,可能都是由不同的开发同学负责的,那么如何做到全覆盖呢?完全靠每个开发同学自己埋点,显然不太现实。时间和工作量不是最大的问题,要保证数据的统一和不遗漏才是最大的麻烦所在。

其次,是流程上的全覆盖。用户的行为是有前后关联关系的,独立的用户行为的采集统计固然重要,比如PV/UV这种用来骗取投资的用户流量类数据。与此同时,完整的用户行为链路的分析也是必不可少的,比如购买决策链路,会场活动效果分析,广告流量来源去向统计等等。

这时候,往往就需要在行为日志的采集过程中,附加必要的关联信息来实现行为的串联。

你可能会想,那就让所有的业务用统一的框架接口来采集日志,需要什么用于关联业务的信息,自己记录不就好了呗,听起来也没多难。

确实如此,事实上,各种各样的流量入口平台,第三方网站/应用统计服务提供商等也都提供了类似js脚本,日志服务接口,日志采集SDK等手段来标准化这件事。比如Goggle analytic,百度统计,Talking Data,Growing IO等等。从这个角度来看,这种方案本身,还是比较标准的,你自己开发起来,模式也不会偏离太多。

但是,大方向没问题,不代表实现起来也很容易。要做到前面所说的全覆盖的能力,JS也好,SDK也好,就得做到尽可能的降低对业务代码的侵入,特别是基础的页面浏览类日志,最好能做到无需业务方主动调用相关代码逻辑。因为但凡需要业务方主动配合,开发代码才能完成的日志采集方案,就有可能因为各种原因,遗漏(忽视)开发这部分代码,造成统计的遗漏。

而业务流程的串联,也不仅仅是附加关联信息那么简单。举个例子,比如想要做页面来源跟踪,有很多种方案,比如植入Cookie,然后在具体页面采集cookie信息判断初始来源,比如在入口或落地页面生成一个唯一标识ID,然后将这个ID作为参数向后传递等等。这些方案,在某些场景下也的确是可行的,但是一旦遇到多条业务链路可能有交叉,或者链路逻辑变化频繁的情况,这些方案的逻辑就有可能随时被打破了。

因此,通用,可靠,维护代价低,这三者在日志采集的方案设计中,通常是最重要的考量因素。当然,实际情况下,在一些业务场景中,这三者可能很难同时做到,也就不排除某些链路需要定制化处理的可能性。

此外,还有很多问题,这里先不单独提出来讨论了,在下面介绍我司的一些实践和方案的过程中一并阐述。

我司的用户行为日志采集方案

整体流程

我司日志采集的整体流程也很标准:页面浏览类日志,在Web端使用js采集页面信息,然后向日志服务器特定URL发起一次http请求,将采集到的数据作为参数传递过去。日志服务器响应请求,记录信息,落盘。服务端日志采集Agent收集日志,再汇总发送给下游链路,比如Kafka消息队列之类。

为了减少对业务代码的侵入和保障页面的全覆盖,js脚本是在服务器后端通过模版嵌入到每一个页面的。

而用户点击交互类日志,还是需要业务方自行处理业务逻辑,然后调用标准接口发送给日志服务器。

APP端则通过SDK提供接口给业务方,封装底层组件的差异和日志Log方式的具体实现,降低开发难度

日志埋点的管理

不论是浏览类日志还是点击交互类日志,都会打上特定的事件ID进行标识,便于后续统计,细节后面再说。

这些ID标识,浏览类的,大多都是按规则自动生成的,也有重点页面是通过注册预先指定的(比如商品详情页什么的)。而点击交互类日志的事件ID,则都是通过后台,统一注册登记管理,生成唯一ID后,再由业务开发方通过SDK记录下来的。

在Web端,埋点的实施是即时生效的,服务器端代码修改以后,在客户端浏览器里下一次加载页面时就能生效(当然,如果你有CDN静态资源缓存的机制,还要考虑如何淘汰旧的缓存资源)。

而在APP端,往往需要通过版本发布的流程才能更新代码,所以埋点的实施,需要提前规划,也无法动态更新。这时候就有人提出动态埋点的技术了,多数情况下,绝大多数所谓的动态埋点技术,也就是先全部无差别的把所有可能的埋点位置先埋上代码,先不启用,需要的时候再带开开关。

我司在APP端目前也还没有做到动态无痕埋点,所以交互类日志埋点的全覆盖,还是依靠业务上线前预先规划统计需求,制定埋点方案,注册管理,测试验证这样的流程来保证的。在业务快速变化的过程中,这么琐碎的事务,光靠人工来保证也是比较困难的。为此,我司也针对性的开发了专门的管理后台来串联相关工作流程。

PV UV和独立交互类事件统计

PV UV 类的独立页面统计分析,理论上来说,通过页面访问记录,获取URL进行正则匹配来做也是可以的。但是这样做也会存在很多问题,比如正则匹配的计算效率比较低,大量页面的情况下URL正则匹配的规则难以管理,规则之间互相之间会不会重叠?一些动态生成的页面,URL甚至可能无法通过正则匹配进行合理的归类,新业务的页面URL如何保证不和原有业务的匹配规则不冲突等等。总之,通过URL进行正则匹配,虽然可行,但是维护代价极高。

所以,更好的方式是给各个页面赋予一个唯一的ID,当然考虑到统计的需要通常是按一类页面进行统计,所以这个唯一ID一般也是赋予同一类页面,而不是每一个具体的页面实例的。(比如搜索图墙,每次用户搜索生成的数据都是不一样的,当然没办法也没有必要给每个搜索结果页面一个唯一的ID标识)

而用户点击交互类的日志,也是如此了,交互事件当然可以有可描述的文本,但是从统计的角度来说,还是赋予每个事件一个唯一ID比较靠谱一点。

有了ID,在后续统计中,对各个ID,进行分组聚合就好了。

用户浏览行为链路跟踪方案

日志采集与用户行为链路跟踪


相比通过独立的单条日志就能完成的简单汇总类统计来说,用户浏览行为的链路跟踪,就麻烦很多了。比如想要知道用户是如何到达一个特定的商品页面的,是通过首页的广告位,还是通过会场活动,抑或是首页/图墙/商铺/详情这样的浏览链路过来的,这就需要串联多条日志才能完成相关的分析工作。

举一个实际的场景例子,作为电商类平台,往往需要跟踪一个订单的下单链路,也就是所谓的订单来源分析,做什么用呢?用途也很多,和钱挂钩的,比如要统计CPS广告推广的费用啊。

这种业务场景,透过URL匹配或者单纯的页面ID来做就会比较棘手,因为用户的浏览行为可能是反复随机跳转的,可能存在重复的浏览行为路径。如何鉴别他是通过哪条路径过来的呢? 对日志做一下时间排序可能是一种解决方案,但是日志如果存在乱序到达,或者丢失的情况,如何能够发现呢。总之,做也是可以,但是代价和可靠性都会是比较大的问题。

当然,也并不是所有的链路分析场景都需要进行多跳分析,传统的页面转换率分析这种群体行为的聚合统计类分析,只涉及到一次页面跳转的来源去向两个页面,与具体跳转链路无关,还是容易处理的。

对于类似订单来源这种多次跳转类行为的精确分析,很容易想到的一种方式就是在作为可能来源的入口页面,埋下一个唯一标识,然后一路透传到最后的订单页面为止,这样事后统计分析时,省去页面跟踪的过程,直接获取这个标识就好了。

理论上,这也是可行的,关键是怎么透传这个参数?比如通过cookie记录,或者生成一个唯一的TraceID,然后一路通过URL传参往下游发送。对于cookie来说,一方面种cookie的代价比较高,另一方面个数也是有限的。而对于URL传递TraceID参数这种方式来说,意味着业务链路上的所有页面都要特殊处理这个参数,继续往下游传递,一来代价更高,二来用户的浏览行为很随意,三来业务的流程也随时可能变更,因此这种方案的维护代价和可靠性也是堪忧的,而且如果需要跟踪的业务流程类型越来越多,这种ID和Cookie的方式也是无法扩展的。

所以,个别业务链路这么处理可能可以,作为通用的解决方案还是不行的。

那么怎么做呢? 我司当前的方案是仿照阿里的SPM编码方案。 这个方案,阿里对外的文章都是语焉不详的,我估计稍微有点敏感,毕竟需要考虑刷流量作弊等问题,不宜过多宣传,原理介绍得太多,不是要被人钻空子么 ;)不过其实有心人真想分析一下也是不难的。所以大概说一下我觉得也没有什么大不了的。当然,还是不要以我司为例了,以阿里的SPM方案为例吧:

SPM编码是用来跟踪页面模块位置的编码,早期的spm编码由4段组成,采用a.b.c.d的格式,后来添加了e字段,所以总共5个字段组成,你可以在阿里系几乎所有的业务页面中看到spm参数,具体怎么用这几个字段其实还是根据不同的业务场景来划分的,并不完全一样。不过,对于多数的业务场景来说,大致相同,比如,以下面这个在淘宝首页点击中间Banner广告的行为为例

日志采集与用户行为链路跟踪


在点击广告打开的页面的URL上,你会看到 spm=a21bo.50862.201862-1.d1.5dcec6f7XFdlFJ 这样的内容,其中:

  • a字段代表的是站点或者你可以认为是一个大的业务,这里a21bo应该就是淘宝了(相对天猫这种业务而言,具体a字段的值,有时候也会变,这个看淘宝的心情了吧)

  • b字段代表了这个业务下的页面ID,比如这里的50862就是淘宝首页了

  • c字段代表了具体的一个链接在页面中的模块,你就理解为是为了再拆分页面的层次结构就好了,这里的201862-1,201862指的是首页正中的Banner广告位模块,-1是为了进一步定位这是这种轮播位置的第一个坑位。

  • d字段代表的是点击的链接在模块内部的索引位置,这里d1就是第一个位置了(Banner位特殊,只有一个位置)

  • e字段是一个按特定规则生成的UniqueID,比如这里的5dcec6f7XFdlFJ,用来区分不同的Session或者点击的,具体实现也和业务有关,你可以理解为为了区分同一个链接在不同的浏览链路实例中的点击之用。区分这个有什么用呢?理论上可以有太多用途了,比如反作弊等等,有些敏感,不便细谈 ;)


到这里,可以看到SPM就是一个分层级的定位体系,这么做的好处很多,比如根据不同的统计粒度需求,可以摘取特定字段进行汇总,汇总的规则也非常标准,与具体业务几乎无关。

比如需要按页面类型统计PV,那么取a.b两个字段分组聚合就可以了。如果要统计具体页面模块的流量,那么统计到a.b.c字段就好了。要精确定位某一个推荐栏位的效果,就需要用到a.b.c.d四个字段。这里只是举例,实际一些业务如何规划这几个字段的层级,也不完全是绝对映射这种关系的,重要的是理解这种分层的目的和收益。

所以,分级定位的方式简化了普通聚合汇总类分析的难度,但是SPM和用户的浏览行为链路又有什么关系呢?

前面看到,SPM参数唯一标识了特定站点页面模块内部的一个链接,这个参数实际上是在用户点击该链接的时候,自动生成并附加在目标链接的URL地址上的,所以在一个页面的URL上的SPM参数,实际上表示的不是这个页面的SPM参数,而是这个页面的点击来源的SPM参数,也就是上一个页面中打开当前页面所用到的链接的位置参数。所以URL中SPM的abcde五个字段都是上个页面的信息。

至于当前页面的SPM信息,你还没有点击链接呢,cd这两个和具体点击位置相关的字段自然是没有的,但是ab这两个和页面绑定的字段是存在的,e这个session判断标识也是可以提前确定的。

所以,在页面打开以后Log的日志里面,我们可以记下URL中链接来源SPM的5个字段,以及当前页面SPM的abe三个字段。这样通过abe三个字段在页面之间就可以形成一个链表关系,通过追踪这个链表我们就可以还原用户的浏览行为链路了。 如果要具体统计模块位置的流量,再把来源页面的cd字段补上就好了。

这个逻辑是不是有点绕? 的确如此,所以此前在开发过程中,每次和我司的各种客户端开发同学讨论这个方案的时候,都要费不少的口舌。

不过这大概也算是链路跟踪里相对靠谱的方案了,这个方案,阿里系的网站已经沿用和发展了十几年了,模仿这个方案的还有比如美团的MTT参数。当然也有更多的大公司没有采用这种方案,不知道是没有在意过这种方案呢,还是有其它解决手段,抑或只是依靠URL来解决问题。

我猜可能兼而有之,毕竟这套方案实施起来要全站贯穿,全端实现,如果有历史包袱的话,代价确实也是很高的。而URL匹配的方案,虽然计算效率差一些,存在这样那样的维护问题,但是如果不是特别注重个体链路的精确分析的话,辅助以其它手段,绝大多数的业务场景,也还是能找到一些解决办法的,不是完全无解。

SPM的原理很简单,就是用格式化的字段分层定位呗,那么SPM方案的难点在哪里呢?

难点还是在于这几个字段具体值的规范化,如何尽可能减少对业务方代码逻辑的侵入。参数已经细化到每一个链接,自然不可能手动维护每个字段的值,这就需要尽可能自动化的生成这些参数,而对于特定的页面或者模块还要保留自定义的能力,以备特殊用途之用。

如何自动化生成各字段的ID信息,这就不是一件简单的事了,不同的字段,不同的客户端环境:Web/IOS/安卓,不同的控件对象,如何生成唯一的ID标识,又不需要业务方太多的干预或者配合,这就要各显神通了。。。

随便举一些例子:

A字段好说,毕竟站点/业务 级别的种类不会太多,直接预定义好,写入页面的后台公共模版就好了。

B字段呢?比如在Web端,可以采用后台具体页面模版的URL的Hash值,找一个不容易冲突的Hash函数基本就可以了。

那么C字段呢?C字段的ID,通常只需要页面内部唯一,所以ID值本身自动生成一个问题不大,倒是如何确定一个模块的范围是比较大的问题?可行的方案,比如在Web页面模版中,可以通过对页面标签添加特定属性的方式来标识模块范围,然后在点击时再自动为这个标签生成一个固定的ID,然后再根据这个标签的范围计算模块内部链接的D字段索引值。

但是,也不是所有的模块ID都只需要页面内唯一就没问题了,有些模块是跨页面共享的,这种场景是预定义一个特殊ID加以标识,做到全局唯一,还是依然只考虑页面内唯一,那就看业务统计的需要了。

简单来说,就是既要保留自定义的能力,又要在不打算自定义的场景下,找到一个自动生成唯一且固定的标识的途径(UUID这种纯粹随机,每次生成结果都不一样的值当然是不行的),以免业务方需要自己生成和维护ID列表。

而植入和串联这些ID的过程,也有各种各样的问题要解决,特殊定制化处理都不难,难就难在要以通用的方式,标准化的处理,总之,一切都是要围绕着降低维护代价来考虑。

其它问题

上一篇的读书笔记在讨论阿里的日志采集链路的过程中,其实也已经提到了一些问题。

比如用户在客户端进行的页面回退行为的识别,需要对这种行为加以识别和筛选。PC端问题少一点,因为可以同时打开多个页面,用户回退的行为会少一些。而在APP端,通常只能同时打开一个页面,所以比如从详情页回退到商品列表页面,再次打开另一个详情页,这种情况就会很普遍。很显然,要分析页面转化率,你不能认为商品列表页面的来源是上一个详情页吧,所以肯定要对这种行为进行修正处理。至于怎么识别和修正这种行为,细节就不讨论了。

Hybrid混合模式的处理:H5和Native页面混合使用的场景越来越普及,虽然用户未必能够感知,但是从原理上来说,这两种页面的日志采集方案通常是两套架构,互相之间的日志流程也往往是隔离的,但是在链路跟踪的场景下,他们的日志必须要统一处理才能正确的复现用户的行为路径。

所以通常是要自动识别H5页面的运行环境,并且打通H5跳转到Native和Native跳转到H5两个方向的页面数据传递,否则在Hybrid场景下,行为链路的日志就会被打断了。

日志采集与用户行为链路跟踪


再有,比如当存在页面301/302 重定向的行为时,链路参数怎么处理?当要跟踪订单成交链路时,用户是先加了购物车,过后,再从购物车里下的订单,又怎么追踪原始成交链路?还有更多的类似问题,都是用户行为日志采集过程中需要解决的。虽然每一个点,单从技术的角度来说,都未必很难,但要做到通用,完善,可靠,却不是一两天可以搞定的事情了。因此用户链路行为跟踪和分析的日志采集,会是一个随着业务的发展,需要持续改进的工作。

小节

我司在用户行为链路跟踪分析方面,日志的采集方案主要参考了阿里的SPM的思想。前前后后实践了也有*年的时间了,原理和思想本身并不复杂,但是在方案完善的过程,包括历史方案的兼容迁移过程中,还是花费了不小的力气,填补了各式大坑小坑。

总体来说,该方案,在各种精确链路追踪,来源分析,活动统计等业务场景中都有不错的表现,不过,在应用模式上,我司的实践还不够充分。不光是从标准化自动化的角度来看,也包括业务的应用场景和相关链路数据价值的进一步挖掘方面,都还有很大的发挥和改进空间。

-----------

常按扫描下面的二维码,关注“大数据务虚杂谈”,务虚,我是认真的 ;)

日志采集与用户行为链路跟踪