前言
酷家乐在2022年之前,整个数据仓库建立在阿里的odps之上(存储oss,计算调度maxcomputer),所有的埋点都是离线T+1入库。
在2022年下云迁移到自建机房的过程中,将埋点链路进行了重构,完成了所有埋点准实时(20分钟级别)写入到数据仓库中。
这里对改造方案进行分享。
埋点改造
下云前的埋点离线方案
埋点流程
流程说明
LMS服务
酷家乐内部的一个埋点系统,主要提供以下信息
- 每个服务的日志目录地址以及需要通过何种方式被采集
- 每个服务和埋点直接的对应关系,也就是每个服务会输出哪些埋点
- 每个埋点的元信息,主要包含字段信息,创建者,上线时间等等
neverland服务
酷家乐内部的一个数据同步系统,在埋点处理流程中,主要提供以下功能
- 根据lms上的服务信息,动态生成oss到ods的同步任务,这里为datax任务,一个服务对应一个datax任务,写入到一个ods表格中(自动创建),每个服务会有多个实例,所以ods表格为二级分区,加了分区用来表示实例信息。
create table ods_table_service_name
(
column1
)
partitioned by (
ds string comment '日期',
instance string comment '实例信息');
- 根据服务和埋点的绑定关系,以及每个埋点的字段等元信息,动态生成ods到dwd的埋点解析MR任务,一个ods表(也就是一个服务)对应一个MR任务,因为一个服务会打多个埋点数据,一个埋点的数据也有可能来自多个服务,所以会写出到多个dwd埋点表格(自动创建),并且dwd也为二级分区,加了分区用来表示来自哪个服务。
create table dwd_table_track_name
(
column1,
column2,
……
)
partitioned by (
ds string comment '日期',
service string comment '服务信息');
该方案的痛点
痛点一:时效性
所有埋点都是T+1入库,当业务方想要快速查看当天埋点数据时看不到,必须等到第二天。
痛点二:稳定性
oss-》ods:因为服务的采集信息是在lms上面配置的,每天的任务个数(服务数量)是动态读取lms的信息后自动生成任务,一个服务一个job,不方便在数仓dag中创建一个服务一个节点,于是这里就把生成任务和调度任务放到了一个大shell单节点中,通过调用内部的nl服务来生成以及调度这些任务。
ods-》dwd:和oss-》ods类似,服务和埋点之间的关系等是lms上动态获取的,每天都不同,服务和埋点之间是多对多的关系,每个dwd埋点表可能由上游多个ods(服务)产生,也不方便在数仓dag中创建一个埋点一个dwd节点,于是这里也将所有任务放到了一个大shell单节点中,通过调用内部的nl服务来实现。
这里就出现了两个大单节点任务,导致所有需要依赖埋点表的数仓任务,都需要依赖这个大dwd节点,这两个节点就成了定时炸弹,一旦出现问题,下游几乎所有节点都受影响。重跑的代价又巨大,需要将两个大节点,也就是所有埋点重跑。
下云后实时的方案
流程说明
当有新埋点需要上线时,用户只需要在lms上进行注册,后台会自动进行收集,创建hive表,创建DAG调度节点。
目前大部分埋点表还是hive表,一小部分因为数据时效性的要求更换成了最新流行的paimon表,这里主要介绍hive表的流程。
hive表主要由两部分组成:
- 数据(hdfs)
- 元数据(mysql-metastore)
这里选择了将两部分内容独立进行写入
-
数据:通过flinkjob实时写入hdfs,ck目前设置了20分钟。
-
元数据:通过每天的数仓dag定时任务,进行创建或更新。
埋点元数据更新节点
该节点为shell离线定时调度节点,远程调用dt-wrapper服务。
调用A
读取LMS上最新的所有埋点信息和HIVE埋点表进行对比,对比后会有以下操作:
-
LMS上有,HIVE中没有的新埋点
- 创建新的hive表
- 创建埋点check节点
- 创建埋点ready节点
-
LMS和HIVE都已经存在的老埋点,会进行字段信息的对比,将最新的字段信息同步到hive表
埋点check节点
一个埋点一个节点,该节点为shell节点,远程调用dt-wrapper服务。
调用B
校验该埋点的数据和hive元数据是否就绪,校验点主要如下:
- 3个flinkjob-A,B,C消费kafka的lag值
- 每个埋点处理的最新时间
- 该埋点当前业务时间的hive分区元数据是否已经添加
只有通过check之后才表示该埋点的数据以及元数据都已经就绪,以此来避免实时任务数据未及时写入,而数仓DAG确开始计算,导致下游计算数据丢失。
埋点ready节点
一个埋点一个节点,该节点为spark节点,需要该节点的主要原因如下:
- flink写入链路中,为了方便维护,jobA和jobB我们使用的是at-least语义,只有jobC使用exactly-once语义,所以整条链路at-least语义,对于一些重要埋点,可以在该节点中添加去重逻辑
- 目前flinkjob-C的ck间隔为20分钟,对于一些小埋点,即使20分钟一个文件,也都是小文件,所以可以在该节点中添加小文件合并逻辑
flinkjob说明
flinkjob-B和flinkjob-C都是可以开启多个,并行处理不同的埋点数据。
flinkjob-A
将采集的埋点按照日志等级分流到不同topic,主要有analyse,info,warn,error等级别,对于数仓埋点值需要analyse级别的埋点,其他级别的埋点供监控等其他需求使用。
flinkjob-B
在mysql中定义了每个埋点->topic-partition 级别的映射(目前还不能自动调整映射关系,人为不定时根据每个埋点的数据量来调整映射关系)
- partition级别的映射
- 多对多的映射
提前进行shuffle到partition级别,在下游flinkjob-C读取写入到hdfs的时候不再需要发生shuffle,控制写入hdfs小文件的数量。
根据映射关系写入到对应的topic-partition中。
flinkjob-C
读取kafka中埋点数据,根据lms的埋点元数据,解析后已orc格式的方式写入到hdfs指定目录中(天级别分区),
由于flinkjob-B已经将数据shuffle到topic的partition中,所以该job不再进行shuffle,直接将数据写入。
对于离线方案的痛点是否解决
痛点一:时效性
目前埋点时效性由filebeat和3个flinkjob决定,除了flinkjob-C是20分钟,其余都是秒级别,已经达到了我们目前对时效性的要求。如果有更高的时效性要求可以走写入paimon的逻辑。
整体从T+1->20分钟。
痛点二:稳定性
目前在数仓DAG中,每个埋点都是独立的节点,下游只需要依赖自己需要的埋点节点即可,没有了改造前的大埋点节点这个炸弹。
不过目前还有个单点就是flinkjob-A,但是该任务逻辑想当简单,按照日志等级分流写入不同的topic,出问题的概率相当小,上线到目前2年时间,没有出现过问题。
整体在运维容错性上也是大大提升,满足了我们目前的需求。
除了离线方案的痛点解决外,还带来了哪些提升
数仓DAG整体提前
原有ods和dwd两个大单节点都是在夜晚运行,由于需要等到所有埋点处理完成,所以运行时间比较久,需要3小时左右,也就是埋点就绪需要等到03:00,下游才能运行。
更换为实时写入后,计算和写入压力均摊到了一整天,每个埋点都独立进行是否就绪校验,一旦通过下游就可以开始计算,一般凌晨00:30所有埋点就可以就绪。
最后
经过这波改造重构后,对我们的收益还是很大的,不管是时效性还是稳定性上都有较大幅度的提升。