用户行为分析系统利用用户的行为明细数据针对用户的有序行为做分析,是一个灵活度较高的实时分析系统,具备参数筛选、人群筛选、多维度拆分、群组拆分等功能。系统结构如图1.1所示,具有事件分析、漏斗分析、归因分析、路径分析、行为跟踪五个基础的分析功能,以行为明细数据作为分析的基础元数据,以群组数据作为分析人群筛选数据。在多种计算引擎的尝试及性能对比下[1],最终选用ClickHouse作为计算引擎。
图1.1 用户行为分析系统结构图
ClickHouse的表分为两种:分布式表、本地表。分布式表是逻辑上的表,可以理解为数据库中的视图,一般查询都查询分布式表。分布式表引擎会将我们的查询请求路由本地表进行查询,然后进行汇总最终返回给用户;本地表是实际存储数据的表。
ClickHouse集群结构如图2.1所示,依靠ReplicatedMergeTree引擎族与ZooKeeper实现了复制表机制,成为其高可用的基础。依靠Distributed引擎实现了分布式表机制,在所有数据分片(shard)上建立视图进行分布式查询。依靠ZooKeeper实现每个shard中的本地表之间的数据同步。
图2.1 ClickHouse集群结构图
Hive到ClickHouse采用MapReduce传输数据,推数流程如图3.1所示,从hive读取到的数据根据用户id路由并写入所有shard的其中一个本地表中,最后由Zookeeper完成各shard本地表之间的数据同步。数据根据用户id路由的目的是为了实现shard之间数据的解耦以提高查询效率,第五章将详细描述。
ClickHouse的分布式表也具有分发数据的功能,但直接向分布式表写入数据存在着以下弊端:
分布式表接收到数据后会将数据拆分成多个parts,并转发数据到其它服务器,会引起服务器间网络流量增加、服务器merge的工作量增加,导致写入速度变慢,并且增加了Too many parts的可能性。
数据的一致性问题,先在分布式表所在的机器进行落盘,然后异步的发送到本地表所在机器进行存储,中间没有一致性的校验,而且在分布式表所在机器时如果机器出现宕机,会存在数据丢失风险。
数据写入默认是异步的,短时间内可能造成不一致。
对zookeeper的压力比较大。
不能达到根据用户id路由的目的。
本文选用在map中做数据路由后直接向本地表写入数据的方案。首先查询System库中的Clusters表,获取集群的shard信息(主要是shard中所有设备的ip),接着将从hive表中读取到的数据按一定规则(用户id % 集群shard个数)路由存储到相应shard的batch中,当batch的数据量达到设定值时即可向对应的shard推送数据。从Clusters表获取到的对应shard的一组设备,向其中一个设备的本地表推送数据,若失败则尝试其他设备。
图3.1 Hive2ClickHouse推数流程图
用户数据包括行为明细数据和群组数据,行为明细数据即用户某个时间点发生某个行为的记录,群组数据即圈定好或计算出的人和群组关系数据。行为明细数据作为分析的元数据,群组数据作为元数据筛选条件,可实现针对特定人群的行为分析。
行为明细数据记录了用户某个时间点发生的某个行为,由用户id、时间戳、行为属性组成,为了便于管理用户行为明细数据,如图4.1所示,相同的行为属性的行为被打包成事件,事件由事件名及事件组成,同时相同属性的事件归为相同的事件类型。
图4.1 事件用户行为关系图
表字段
事件打包后的行为明细数据由事件类型、事件名、事件属性组成,那么行为明细数据表的字段有:用户id、事件类型、事件名、时间戳、事件属性字段,其中事件属性字段由事件决定,因此事件属性字段设为公用字段,由数据库管理事件及事件属性字段之间的关系。
分区
行为数据的事件打包一方面是为了便于使用者的理解及选择,另一方面是为了数据的快速筛选,即将事件类型作为行为明细数据表的一个分区。
排序
行为分析是在选定的事件中针对用户有时间顺序的行为串的分析,筛选选定事件期望事件的所有数据存储在一起,时间顺序决定了对时间戳期望读取到的数据已经按时间排好顺序,用户的行为串期望同一个用户的数据在存储在一起以减少group by用户的时间。因此事件名、时间戳、用户id作为排序字段,优先级事件名>用户id>时间戳。
群组数据有两种类型:标签群组数据、事件群组数据。标签群组数据为人及群组关系明确的数据,事件群组数据需要从行为明细数据中提取人群数据。群组数据选用bitmap存储节约存储量的同时,位图计算提升计算速率[2]。
标签群组数据
标签群组数据来自DMP计算好的人与群组之间的关系,为了保证群组数据根据用户id路由,标签群组的创建分如图4.2所示为三步,从Hive表推入到ClickHouse的数据以用户id为粒度,接着将用户id为粒度的数据拆分后写入拆分表,最后将拆分表中的数据写入表引擎为AggregatingMergeTree[3]的聚合表中,聚合表以群组作为聚合粒度用户id组合成bitmap。
图4.2 标签群组创建流程图
事件群组数据
由于事件群组数据已经跟随行为明细数据实现了用户id路由,因此需执行标签群组的第三步,从事件中获取到用户与群组之间的关系后将数据写入到表引擎为AggregatingMergeTree的聚合表中即可获得事件群组数据。
基本的用户行为分析场景分为有事件分析、漏斗分析、路径分析、行为跟踪、归因分析,分析基本结构如图5.1所示,首先根据时间范围、事件范围、群组范围筛选出待分析的行为明细数据范围,再以用户id及维度为聚合粒度获得用户行为串,最后对行为串做聚合分析运算。
图5.1 分析基本结构
由于数据根据用户id路由,所以同一个用户的所有数据只可能存储在同一个shard中,不同的shard之间的用户行为串无耦合,因此分析sql可以同时在所有的shard执行,并将每个shard的分析结果累加,实现并行计算。与分布式表的并行计算不同的是,分布式表是并行计算明细层的函数转换并将转换结果汇总做聚合,而shard并行是并行计算函数转换及明细聚合并对聚合结果汇总做累加,因此shard并行具有更高的计算效率。
事件分析即明细数据的聚合运算,是这些分析场景中特殊的一种分析,由于事件分析并不关心用户行为串,只需要对所有用户的事件明细做统计,因此并不需要用到上述的分析基本结构。事件分析是对所有用户的明细数据统计,数据根据用户id路由并不能保证shard之间的待统计数据无耦合,例如拼单场景下的订单量的统计(distinct 订单号),不同shard之间存在具有相同的订单号的情况,因此不能直接将每个shard的计算结果累加。
为了解决此类distinct分析的矛盾,增加一个字段用来标记顺序,实现将distinct字段与人绑定。仍然以拼单为例,将单号与人绑定如图5.2所示,并行计算的情况下以【distinct 订单号】统计订单量结果为2,利用顺序字段即可更正这种错误,即以【distinct (case when 顺序 = 1 then 订单号 else null end)】统计订单量。实际应用中需要统计distinct且不同的人会共享的字段并不多,因此本文选用此方法以空间换时间。
图5.2 拼单场景下单号与人绑定
漏斗分析即一定窗口转化期内用户的转化率的计算,ClickHouse提供了计算漏斗的函数windowFunnel (时间窗口)(时间戳,第一层条件,第二层条件......)[4],官方自带的漏斗函数计算流程如图5.3所示,首先将传入的用户行为串序列按时间戳排序,如果序列传入时已经跑排好顺序此步骤将减少耗时;接着提取序列的层级信息以获得层级序列便于特征向量的提取;然后遍历层级序列生成特征向量,特征向量是在顺序遍历层级序列的情况下记录每一层级符合时间窗口条件的时间戳;最后统计出特征向量中不为空的时间戳个数,作为该用户的最大匹配层级。
图5.3 ClickHouse官方windowFunnel函数计算流程
ClickHouse官方windowFunnel函数虽然能够根据用户行为串统计出该用户的最大匹配层级,但并不能实现维度拆分,本文自定义漏斗维度拆分函数dimFunnel (时间窗口)(时间戳,维度,第一层条件,第二层条件…),能够实现第一层级的维度的拆分功能,dimFunnel函数计算流程如图5.4所示,在windowFunnel的函数的基础上用一个key为维度value为特征向量的map来记录第一层级的各个维度下的特征向量以实现第一层级的维度拆分,最后遍历维度-特征向量map,获取每个维度下特征向量不为空的个数作为每个维度下用户的最大匹配层级。
图5.4 自定义dimFunnel函数计算流程
路径分析即一定会话时间内的用户访问路径的统计,计算流程如图5.5所示,首先对行为串按时间戳排序,若数据按照时间戳顺序排好序存储可减少此步骤的耗时;接着从起始事件往后或者终止事件往前截取有效的行为串并相邻为去重;然后计算前后行为的时间戳的间隔时间,若间隔时间大于会话时间则截断序列;接着截取会话访问时间内序列的有效段,取十条行为记录;最后对所有的序列做筛选,保留满足起始事件、终止事件的序列。
图5.5 路径分析计算流程
行为跟踪又被称为留存分析,即产生A行为的用户在此之后的一定时间内产生B行为的状况统计,ClickHouse官方函数retention(起始条件,后续条件1,后续条件2 …)[5]可以用作留存分析的计算。retention函数的计算流程如图5.6所示,首先初始化结果array;接着array[0]为是否存在满足起始条件的行为,0为不满足,1为满足;然后bian遍历后续条件,在起始行为条件满足的且存在满足第i个后续条件的情况下Array[i-1]=1。
图5.6 ClickHouse官方retention函数计算流程
留存按起始事件的维度划分计算流程如图5.7所示,由于留存特性是只有满足起始条件后续条件才会生效,因此只需对每日的起始事件用一个维度Array记录维度值,对汇总维度值展开至多行后,若此日的起始事件维度Array不包含此纬度值则次日在该维度下的留存清零,即此维度下此日没有满足起始条件的行为。最后以活动为粒度聚合留存结果。
图5.7 留存分维度计算流程
归因分析即用户产生一组行为后对于促成最终目标的贡献度的统计[6],归因分析常用的几类有:首次归因、末次归因、线性归因。归因分析计算流程如图5.8所示,首先将行为串按照时间戳排序,若数据按照时间戳顺序排好序存储可减少此步骤的耗时;接着以目标事件为终点截断行为串,得到一系列以目标事件为终点的行为串;然后遍历行为串获取归因明细数据,归因明细由归因比例、计算指标、维度事件id、维度值组成;最后对归因明细数据以维度事件id及维度值为粒度聚合,归因比例聚合结果除以所有归因比例聚合结果的和为归因比例,计算指标的聚合结果为目标事件的指标归因到该事件维度的值。
图5.8 归因分析计算流程
本文介绍了用户行为分析系统的组成、ClickHouse集群的部署、从Hive到ClickHouse的推数、用户数据及建表以及五种行为分析,以shard数据解耦为前提实现并行计算,虽然计算效率得以提升,但计算消耗线程数为集群shard个数的倍数。官方提供原始函数的同时也开放了自定义函数入口,可通过自定义函数进一步提升计算效率。
The End
如果你觉得这篇内容对你挺有启发,请你轻轻点下小手指,帮我两个小忙呗:
1、点亮「在看」,让更多的人看到这篇满满干货的内容;
2、关注公众号「哈啰技术团队」,可第一时间收到最新技术推文。
如果喜欢就点个👍喔,有您的喜欢⛽️,我们会更有动力输出有价值的技术分享滴;