什么是埋点?

所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语。指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。埋点的技术实质,是先监听软件应用运行过程中的事件,当需要关注的事件发生时进行判断和捕获。

埋点的意义何在?

  • 流量监测(在线情况分析、按时段分析、按来源分析);

  • 构建行为路径, 通过对处理后的信息进行关联,获取用户的整条行为链路;

  • 通过对埋点数据的处理、分析、建模,可以挖掘用户的喜好、需求,判断产品的效果和未来走向;

  • 监控应用运行状态,提供问题跟踪定位的数据支持;

  • 为营销策略提供数据支持;

  • 实施 AB Testing;

  • 作为数据平台中,数据采集的一个不可缺少的环节;

埋点的难点

现在的业务技术架构都不仅仅是单独的一种技术方案能解决的。现在只要是做互联网的公司,其业务系统都会包含如下系统模块:

  • 大前端。这里包含 WEB、HTML5, App(IOS、Android、Hybrid形式)

  • 后端应用系统

  • 服务器系统

埋点的方式

埋点方式多种多样,按照埋点位置不同,可以分为前端(客户端)埋点与后端(服务器端)埋点,其中前端埋点包括:代码埋点、全埋点、可视化埋点。这些埋点方式的比较如下:

e15a381d39e51dd0b9e09a39b00a9d8e.png

前端埋点:

  • 无埋点(全埋点):零埋点成本,抓取用户行为全量数据,任何操作行为都会被上传。数据量大,“噪音”多;

  • 可视化埋点:在页面中操作,选择埋点位置/模块,非开发人员也可以进行埋点;

  • 侵入式(代码)埋点:埋点时需要将数据采集代码写入业务代码中,埋点成本较高,但准确度也更高;

埋点准确性顺序:侵入式(代码)埋点 > 可视化埋点 > 全埋点

常见埋点属性

通常前端是按照页面维度统计埋点的,常见的事件属性如下:

  • uid: 用户id,若用户未登陆,则返回特定标识id

  • url: 当前事件触发页面的url

  • eventTime:触发埋点的时间戳

  • localTime: 触发埋点时的用户本地时间,使用标准YYYY-MM-DD HH:mm:ss格式表示,方便后期直接使用字符串查询

  • deviceType: 当前用户使用的设备类型

  • deviceId: 当前用户使用的设备id

  • osType: 前用户使用的系统类型

  • osVersion: 当前用户使用的系统版本

  • appVersion: 当前应用版本

  • appId: 当前应用id

  • extra: 自定义数据,一般是序列化的字符串,且数据结构应保持稳定

常见埋点事件
  1. 页面停留: 当前页面切换或者页面卸载时, 记录前一页浏览时间

  2. pv: 进入页面时, 页面访问次数,uv只需要根据deviceId过滤

  3. 交互事件: 用户交互事件触发时,比如点击、长按等

  4. 逻辑事件: 符合逻辑条件时, 比如登陆、跳转页面等

性能数据采集方案
  • connectEnd: HTTP(TCP) 返回浏览器与服务器之间的连接建立时的时间戳。如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。

  • connectStart: HTTP(TCP) 域名查询结束的时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和 fetchStart一致。

  • domComplete: 当前文档解析完成,即Document.readyState 变为 'complete'且相对应的readystatechange 被触发时的时间戳

  • domContentLoadedEventEnd: 当所有需要立即执行的脚本已经被执行(不论执行顺序)时的时间戳。

  • domContentLoadedEventStart: 当解析器发送DOMContentLoaded 事件,即所有需要被执行的脚本已经被解析时的时间戳。

  • domInteractive: 当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳。

  • domLoading: 当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的 readystatechange事件触发时)的时间戳。

  • domainLookupEnd: DNS 域名查询完成的时间。如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等

  • domainLookupStart: DNS 域名查询开始的UNIX时间戳。如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和fetchStart一致。

  • fetchStart: 浏览器准备好使用HTTP请求来获取(fetch)文档的时间戳。这个时间点会在检查任何应用缓存之前。

  • loadEventEnd: 当load事件结束,即加载事件完成时的时间戳。如果这个事件还未被发送,或者尚未完成,它的值将会是0.

  • loadEventStart: load事件被发送时的时间戳。如果这个事件还未被发送,它的值将会是0。

  • navigationStart: 同一个浏览器上一个页面卸载(unload)结束时的时间戳。如果没有上一个页面,这个值会和fetchStart相同。

  • redirectEnd: 最后一个HTTP重定向完成时(也就是说是HTTP响应的最后一个比特直接被收到的时间)的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0.

  • redirectStart: 第一个HTTP重定向开始时的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0。

  • requestStart: 返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间戳。

  • responseEnd: 返回浏览器从服务器收到(或从本地缓存读取,或从本地资源读取)最后一个字节时(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳。

  • responseStart: 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳。如果传输层在开始请求之后失败并且连接被重开,该属性将会被数制成新的请求的相对应的发起时间

  • secureConnectionStart: HTTPS 返回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接,则返回0。

  • unloadEventEnd: 和 unloadEventStart 相对应,unload事件处理完成时的时间戳。如果没有上一个页面,这个值会返回0。

  • unloadEventStart: 上一个页面unload事件抛出时的时间戳。如果没有上一个页面,这个值会返回0。

常见性能指标
  • FP: 页面首次绘制时间

  • FCP: 页面首次有内容绘制的时间

  • FMP: 页面首次有效绘制时间,FMP >= FCP

  • TTI: 页面完全可交互时间

  • FID: 页面加载阶段,用户首次交互操作的延时时间

  • MPFID: 页面加载阶段,用户交互操作可能遇到的最大延时时间

  • LOAD: 页面完全加载的时间(load 事件发生的时间)

后端埋点:

后端埋点 指触发了服务端接口调用(如:接口回调成功触发)的事件埋点,如最典型的注册成功事件、付费成功事件。后端埋点对数据的准确度要求更高,同时也可以通过变量字段的扩展支持数据拆分、聚合和下钻。需要强调的是,后端事件一般采集的是已登录状态下的用户行为,如果想使用后端埋点事件作为流程分析的其中一环(如漏斗分析),则可能出现未登录的用户会漏掉的情况。

后端埋点为了避免前端埋点的以下问题:

前端埋点需要对采集的数据压缩、暂存,为减少移动端的数据流量,除一些需要实时上报的重要事件不限制网络环境,其它事件一般只在wifi情况下上报,因此数据会有延迟,丢数据等弊端,而在后端采集数据,由于数据是在内网传输,数据传输的即时性强,丢失数据的风险小。

前端埋点采集程序由于需要常驻,监测实时和延迟埋点上报,不可避免的带来额外的耗电。

前端埋点若要新增或调整采集方案,需要开发人员修改客户端代码,然后发版之后才能解决,受发布周期的影响较大,而且通常用户的版本更新并不会及时,这将导致新方案不能及时覆盖所有用户。虽然现在部分埋点管理后台也支持热配置更新,但功能一般都很弱,只支持一些基础的埋点事件热更新部署,

注意:

很多时候并不把后端埋点独立出来,而是混合在前端埋点中,等用户和服务器端的交互返回结果之后,将结果进行上报。

对一下需要精确采集的数据,比如代金券发放等,实施的时候尽量采用后端埋点,除非后端无法采集到所需要的数据,前端埋点只是用来参考。此外也可以将业务数据库代金券领取数据同步到数据仓库中进行分析。

其它埋点

路径埋点和独立埋点:

这部分的埋点根据业务对路径的追踪需求和SDK的开发能力,可为每个事件设计上下文的路径信息,路径信息的组成一般由页面、控件、行为三部分组成,而路径的深度也不宜太深,一般小于五层。

显性埋点和隐性埋点:

显性和隐性是从用户有感和无感来区分的,有感事件是用户的主动事件,比如展示和点击事件;无感事件主要用来处理后台的数据请求和拉取,用以监控和服务器的数据交互是否正常等,无感事件中常用的是扫描采集,比如app启动之后,扫描各设置开关的状态信息进行上报等

业务埋点和监测埋点:

业务埋点是从业务需求的角度而言,比如产品需要统计某个页面的曝光和点击,算法人员需要的推荐项点击率等;而监测埋点是从业务的流程上来讲的,一般是指隐性的(比如服务器交互的内容拉取情况、本地潜在信息的生成情况等),此外业务埋点中的关键部分也可以用作监测埋点。

最理想的埋点方式?

回到一开始的问题:何种埋点方式最理想呢?

正如同硬币有两面,任何单一的埋点方式都存在优点与缺点,企图通过简单粗暴的几行代码/一次部署、甚至牺牲用户体验的埋点方式,都不是企业所期望的。要满足精细化、精准化的数据分析需求,可根据实际需要的分析场景,选择一种或多种组合的采集方式,毕竟采集全量数据不是目的,实现有效的数据分析,从数据中找到关键决策信息实现增长才是重中之重。

因此,数据采集只是数据分析的第一步,数据分析的目的是洞察用户行为,挖掘用户价值,进而促进业务增长,故最理想的埋点方案是根据根据不同的业务和场景以及行业特性和自身实际需求,将埋点通过优劣互补方式进行组合,比如:

  • 1、代码埋点+全埋点:在需要对落地页进行整体点击分析时,细节位置逐一埋点的工作量相对较大,且在频繁优化调整落地页时,更新埋点的工作量更加不容小觑,但复杂的页面存在着全埋点不能采集的死角,因此,可将代码埋点作为辅助,将用户核心行为进行采集,从而实现精准的可交叉的用户行为分析;

  • 2、代码埋点+服务端埋点:以电商平台为例, 用户在支付环节,由于中途会跳转到第三方支付平台,是否支付成功需要通过服务器中的交易数据来验证,此时可通过代码埋点和服务端埋点相结合的方式,提升数据的准确性;

  • 3、代码埋点+可视化埋点:因代码埋点的工作量大,可通过核心事件代码埋点,可视化埋点用于追加和补充的方式采集数据。

埋点上报方式

对于一个埋点方案来说,数据上报有两个点需要着重考虑:

  • 对跨域做特殊处理。

  • 页面销毁后,如何还能够将未上传的埋点数据成功上报

图片请求

有下面几点优势:

  • 没有跨域问题,一般这种上报数据,代码要写通用的,img 天然支持跨域;(排除 ajax)

  • 不会阻塞页面加载,影响用户的体验,只要 new Image 对象就好了, 通过它的onerror和onload事件来检测发送状态;(排 除 JS/CSS 文件资源方式上报)

  • 在所有图片中,简单、安全、相比PNG/JPG体积最小;(比较 PNG/JPG)(tip:最小的BMP文件需要74个字节,PNG需要67个字节,而合法的GIF,只需要43个字

  • 这种使用方式也存在缺陷。首先对于src 中的URL内容是有大小限制的,太大的数据量不适用。详细看这里。其次,在页面卸载的时候,若存在数据未发送的情况,会先将对应的数据发送完,再执行页面卸载。这种情况下,会在体验上给使用者带来不方便。

GET 请求

GET把参数包含在URL中,也就是说我们的上报的数据是在一个url 参数中或者是几个参数中,例如 ?data=XXXX 这里的data 就是我们上报的数据

GET 请求 最大的特点就是简单,但是同时也带来了很多其他的问题,首先是安全问题因为GET 请求参数被暴露在IURL 中,GET请求只能进行url编码,而POST支持多种编码方式,其次GET请求在URL中传送的参数是有长度限制的,也就是如果你上报的数据内容比较多,可能会被截断。

POST 请求

POST 请求 相比GET 请求首先就是更加安全,其次是支持多种编码,而且所能发送的数据量也更大,看起来是个不错的选择,但是还是不如图片请求好

埋点管理设计

806c42fb17810d040dfad4f8bd2f9e5b.png下面是APP 端的一个例子

2c542e78daaaf5cfeac60b347384abb2.png

事件模型

9270aea45bcbf2a1dd0ed7d53448e072.png

事件的设计

下面分别是 H5、APP 、小程序 端埋点的一个设计

0a9bc861d9bad9be8da96348dc34d8db.png

基本规范

我们在设计的时候要注意一些基本的规范,例如我们属性的命名,这样才能可以更好的维护

fc459faad99310dfd0bf00c5a50efde0.png

预置属性

d0f1bf20406fe6e861b6ec417cebba08.png

设计原则

整个埋点的设计我们应该遵循一下几个原则,从而可以更好的维护和管理整个埋点系统

  • 通用基础事件

埋点时间能通用则不单独埋点,不是说单独埋点越多越好,我们应该尽可能的从上层设计比较通用的事件,这样方便复用。

  • 重要事件

重要事件单独处理,统一上报,保证采集的可用性

  • 业务主流程

对于主要的业务流程,我们可以设计独立的事件,从而方便更好的分析

da6d51bc77c03afd074f408e6ba6482a.png

后端 日志收集系统架构设计

8370c62eab9b41885b4a9a20586347e9.png具体日志发送流程如下图:

57a5fc7ee53658a467eeed705c1f8e76.png

抽象出来架构模型如下:

a12029326543320921ea50bf8f0e59e6.png

这是一个再常见不过的架构了:

(1)Kafka:接收用户日志的消息队列

(2)Logstash:做日志解析,统一成json输出给Elasticsearch

(3)Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能。

(4)Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK stack的重要原因。

(5)Zookeeper: 状态管理,监控进程等服务

日志从产生到检索,主要经历以下几个阶段:采集->传输->缓冲->处理->存储->检索

228a3a175abf083965a058fdb6600de6.png

日志接入

日志接入目前分为两种方式,SDK 接入和调用 Http Web 服务接入

SDK 接入:日志系统提供了不同语言的 SDK,SDK 会自动将日志的内容按照统一的协议格式封装成最终的消息体,并最后最终通过 TCP 的方式发送到日志转发层(rsyslog-hub);

Http Web 服务接入:有些无法使用 SDk 接入日志的业务,可以通过 Http 请求直接发送到日志系统部署的 Web 服务,统一由 web protal 转发到日志缓冲层的 kafka 集群。

日志采集

可选 rsyslog,flume

日志缓冲

Kafka 是一个高性能、高可用、易扩展的分布式日志系统,可以将整个数据处理流程解耦,将 kafka 集群作为日志平台的缓冲层,可以为后面的分布式日志消费服务提供异步解耦、削峰填谷的能力,也同时具备了海量数据堆积、高吞吐读写的特性。

日志切分

日志分析是重中之重,为了能够更加快速、简单、精确地处理数据。日志平台使用 spark streaming 流计算框架消费写入 kafka 的业务日志,Yarn 作为计算资源分配管理的容器,会跟不同业务的日志量级,分配不同的资源处理不同日志模型。

整个 spark 任务正式运行起来后,单个批次的任务会将拉取的到所有的日志分别异步的写入到 ES 集群。业务接入之前可以在管理台对不同的日志模型设置任意的过滤匹配的告警规则,spark 任务每个 excutor 会在本地内存里保存一份这样的规则,在规则设定的时间内,计数达到告警规则所配置的阈值后,通过指定的渠道给指定用户发送告警,以便及时发现问题。当流量突然增加,es 会有 bulk request rejected 的日志会重新写入 kakfa,等待补偿。

日志存储

原先所有的日志都会写到 SSD 盘的 ES 集群,logIndex 直接对应 ES 里面的索引结构,随着业务增长,为了解决 Es 磁盘使用率单机最高达到 70%~80% 的问题,现有系统采用 Hbase 存储原始日志数据和 ElasticSearch 索引内容相结合的方式,完成存储和索引;Index 按天的维度创建,提前创建index会根据历史数据量,决定创建明日 index 对应的 shard 数量,也防止集中创建导致数据无法写入。现在日志系统只存近 7 天的业务日志,如果配置更久的保存时间的,会存到归档日志中;对于存储来说,Hbase、Es 都是分布式系统,可以做到线性扩展。

写给前端同学的疑问

  • ElasticSearch为何查询速度快

在mysql中,是以id简历b+树索引,然后通过目录页对应到数据页,然后找到数据。对于传统的增删改查(用id)没有任何问题,速度也很快,但是对于全文检索来说,就很尴尬。比如查询like %北大%。这样是走不到索引的,需要全表扫描。但是对于es来说,这就好办多了。

倒序索引:以name为倒序索引来看。

60526daf44cacb8127e53f3e8ef45382.png

我们是将内容进行了分词(这里是最细粒划分)。然后指向了我们document的一个唯一的标识,能够找到位置的地址。

这样,当我们在程序发出一个查询请求后,比如“北大青年”。首先会把这个查询内容分词:“北大”、“青年”。然后就找到对应的数据[1,2,3]。这三条数据了,比我们在mysql中模糊查询快的多。这是其中的一个原因。

我们将“北大”、“河北”、“大学...这样的叫做term。如果有很多个term,那么我们如何找到对应的term呢。我们以term是英文为例:假如有Carla,Sara,Elin,Ada,Patty,Kate,Selena。

第一个方法:遍历?遍历是不可能遍历的,这辈子都不可能遍历的。

第二个方法:采用二分查找(悄悄的告诉你,mysql的inndb中在目录页的查找过程中和数据页的查找对应的数据中均有体现)可以用 logN 次磁盘查找得到目标。但是磁盘的随机读操作仍然是非常昂贵的(一次random access大概需要10ms的时间)。而相比于mysql,term的dictionary要大得多。无法完整地放到内存里,于是就有了第三个方法。

第二个方法:term index。term index有点像一本字典的大的章节表。如果所有的term都是英文字符的话,可能这个term index就真的是26个英文字符表构成的了。但是实际的情况是,term未必都是英文字符,term可以是任意的byte数组。而且26个英文字符也未必是每一个字符都有均等的term,比如x字符开头的term可能一个都没有,而s开头的term又特别多。实际的term index是一棵trie 树:

27d2757373047c57acd870dc3ec787e2.png

这里只考虑前缀并不考虑完整的分词字,例子是一个包含 "A", "to", "tea", "ted", "ten", "i", "in", 和 "inn" 的 trie 树。这棵树不会包含所有的term,它包含的是term的一些前缀。通过term index可以快速地定位到term dictionary的某个offset,然后从这个位置再往后顺序查找。再加上一些压缩技术(搜索 Lucene Finite State Transducers) term index 的尺寸可以只有所有term的尺寸的几十分之一,使得用内存缓存整个term index变成可能。整体上来说就是这样的效果。

023da3589b8d6c775f9b485c27a7f057.png

这种方式就很快就能够查找到对应的分词,然后在对应的分词就找到了对应的主键,然后就可以直接找到对应的数据了。

  • HDFS和HBase区别

HDFS容错率很高,即便是在系统崩溃的情况下,也能够在节点之间快速传输数据。HBase是非关系数据库,是开源的Not-Only-SQL数据库,它的运行建立在Hadoop上。HBase依赖于CAP定理(Consistency, Availability, and Partition Tolerance)中的CP项。

HDFS最适于执行批次分析。然而,它最大的缺点是无法执行实时分析,而实时分析是信息科技行业的标配。HBase能够处理大规模数据,它不适于批次分析,但它可以向Hadoop实时地调用数据。

HDFS和HBase都可以处理结构、半结构和非结构数据。因为HDFS建立在旧的MapReduce框架上,所以它缺乏内存引擎,数据分析速度较慢。相反,HBase使用了内存引擎,大大提高了数据的读写速度。

HDFS执行的数据分析过程是透明的。HBase与之相反,因为其结构基于NoSQL,它通过在不同的关键字下进行排序而获取数据。

e49cf29e632dd38043f199d59e43b125.png

最后, 送人玫瑰,手留余香,觉得有收获的朋友可以点赞,关注一波 ,我们组建了高级前端交流群,如果您热爱技术,想一起讨论技术,交流进步,不管是面试题,工作中的问题,难点热点都可以在交流群交流,为了拿到大Offer,邀请您进群,入群就送前端精选100本电子书以及下方前端精选资料 添加 下方小助手二维码就可以进群。让我们一起学习进步.

86d0a15b551088cea706e086f60621c7.png

703b75960caab175d6ae8c5681ee5daf.png


推荐阅读

(点击标题可跳转阅读)

[极客前沿]-你不知道的 React 18 新特性

[极客前沿]-写给前端的 K8s 上手指南

[极客前沿]-写给前端的Docker上手指南

[面试必问]-你不知道的 React Hooks 那些糟心事

[面试必问]-一文彻底搞懂 React 调度机制原理

[面试必问]-一文彻底搞懂 React 合成事件原理

[面试必问]-全网最简单的React Hooks源码解析

[面试必问]-一文掌握 Webpack 编译流程

[面试必问]-一文深度剖析 Axios 源码

[面试必问]-一文掌握JavaScript函数式编程重点

[面试必问]-阿里,网易,滴滴,头条等20家面试真题

[面试必问]-全网最全 React16.0-16.8 特性总结

[架构分享]- 微前端qiankun+docker+nginx自动化部署

[架构分享]-石墨文档 Websocket 百万长连接技术实践

[自我提升]-Javascript条件逻辑设计重构

[自我提升]-送给React开发者十九条性能优化建议

[自我提升]-页面可视化工具的前世今生

[大前端之路]-连前端都看得懂的《Nginx 入门指南》

[软实力提升]-金三银四,如何写一份面试官心中的简历

觉得本文对你有帮助?请分享给更多人

关注「React中文社区」加星标,每天进步

14af9432a5b842ce31a49668d055027d.png   

点个赞👍🏻,顺便点个 在看 支持下我吧

Logo

开源、云原生的融合云平台

更多推荐