文 | 石伟 来自字节跳动数据平台开发套件团队
埋点数据流主要处理的数据是埋点,埋点也叫Event Tracking,是数据和业务之间的桥梁,也是数据分析、推荐、运营的基石。
用户在使用 App 、小程序、 Web 等各种线上应用时产生的用户行为数据主要通过埋点的形式进行采集上报,按不同的来源可以分为:
-
-
-
埋点通过埋点收集服务接收到MQ,经过一系列的Flink实时ETL对埋点进行数据标准化、数据清洗、数据字段扩充、实时风控反作弊等处理,最终分发到不同的下游。下游主要包括推荐、广告、ABTest、行为分析系统、实时数仓、离线数仓等。因为埋点数据流处在整个数据处理链路的最上游,所以决定了“稳定性”是埋点数据流最为关注的一点。
字节跳动埋点数据流的规模比较大,体现在以下几个方面:
-
接入的业务数量很多,包括抖音、今日头条、西瓜视频、番茄小说在内的多个App和服务,都接入了埋点数据流。
-
流量很大,当前字节跳动埋点数据流峰值流量超过1亿每秒,每天处理超过万亿量级埋点,PB级数据存储增量。
-
ETL任务规模体量较大,在多个机房部署了超过1000个Flink任务和超过1000个MQ Topic,使用了超过50万Core CPU资源,单个任务最大超过12万Core CPU,单个MQ Topic最大达到10000个partition。
那么在这么巨大的流量和任务规模下,埋点数据流主要处理的是哪些问题呢?我们来看几个具体的业务场景。
在推荐场景中,由于埋点种类多、流量巨大,而推荐只关注其中部分埋点,因此需要通过UserAction ETL对埋点流进行处理,对这个场景来说有两个需求点:
-
-
为了提升下流推荐系统的处理效率,我们在数据流配置ETL规则对推荐关注的埋点进行过滤,并对字段进行删减、映射、标准化等清洗处理,将埋点打上不同的动作类型标识,处理之后的埋点内部一般称为UserAction。UserAction与服务端展现、Feature等数据会在推荐Joiner任务的分钟级窗口中进行拼接处理,产出instance训练样本。
举个例子:一个客户端的文章点赞埋点,描述了一个用户在某一个时间点对某一篇文章进行了点赞操作,这个埋点经过埋点收集服务进入ETL链路,通过UserAction ETL处理后,实时地进入推荐Joiner任务中拼接生成样本,更新推荐模型,从而提升用户的使用体验。
如果产出UserAction数据的ETL链路出现比较大的延迟,就不能在拼接窗口内及时地完成训练样本的拼接,可能会导致用户体验的下降,因此对于推荐来说,数据流的时效性是比较强的需求。而推荐模型的迭代和产品埋点的变动都可能导致UserAction ETL规则的变动,如果我们把这个ETL规则硬编码在代码中,每次修改都需要升级代码并重启相关的Flink ETL任务,这样会影响数据流的稳定性和数据的时效性,因此这个场景的另一个需求是ETL规则的动态更新。
抖音的埋点Topic晚高峰超过一亿每秒,而下游电商、直播、短视频等不同业务关注的埋点都只是其中一部分。如果每个业务都分别使用一个Flink任务去消费抖音的全量埋点去过滤出自己关注的埋点,会消耗大量的计算资源,同时也会造成MQ集群带宽扇出非常严重,影响MQ集群的稳定性。
因此我们提供了数据分流服务,实现上是我们使用一个Flink任务去消费上游埋点Topic,通过在任务中配置分流规则的方式,将各个业务关注的埋点分流到下游的小Topic中提供给各业务消费,减少不必要的资源开销,同时也降低了MQ集群出带宽。
分流需求大多对SLA有一定要求,断流和数据延迟可能会影响下流的推荐效果、广告收入以及数据报表更新等。另外随着业务的发展,实时数据需求日益增加,分流规则新增和修改变得非常频繁,如果每次规则变动都需要修改代码和重启任务会对下游造成较大影响,因此在数据分流这个场景,规则的动态更新也是比较强的需求。
另一个场景是容灾降级。数据流容灾首先考虑的是防止单个机房级别的故障导致埋点数据流完全不可用,因此埋点数据流需要支持多机房的容灾部署。其次当出现机房级别的故障时,需要将故障机房的流量快速调度到可用机房实现服务的容灾恢复,因此需要埋点数据流具备机房间快速切流的能力。
而数据流降级主要考虑的是埋点数据流容量不足以承载全部流量的场景,比如春晚活动、电商大促这类有较大突发流量的场景。为了保障链路的稳定性和可用性,需要服务具备主动或者被动的降级能力。
挑战主要是流量大和业务多导致的。流量大服务规模就大,不仅会导致成本治理的问题,还会带来单机故障多、性能瓶颈等因素引发的稳定性问题。而下游业务多、需求变化频繁,推荐、广告、实时数仓等下游业务对稳定性和实时性都有比较高的要求。
在流量大、业务多这样的背景下,如何保障埋点数据流稳定性的同时降低成本、提高效率,是埋点数据流稳定性治理和成本治理面对的挑战。
上文我们了解了埋点数据流的业务场景和面对的挑战,接下来会介绍埋点数据流在ETL链路建设和容灾与降级能力上的一些实践。
埋点数据流ETL链路发展到现在主要经历了三个阶段。
第一个阶段是2018年以前,业务需求快速迭代的早期阶段。那时我们主要使用PyJStorm与基于Python的规则引擎构建主要的流式处理链路。特点是比较灵活,可以快速支持业务的各种需求,伴随着埋点量的快速上涨,PyJStorm暴露出很多稳定性和运维上的问题,性能也不足以支撑业务增长。2018年内部开始大力推广Flink,并且针对大量旧任务使用PyJStorm的情况提供了PyJStorm到PyFlink的兼容适配,流式任务托管平台的建设一定程度上也解决了流式任务运维管理问题,数据流ETL链路也在2018年全面迁移到了PyFlink,进入到Flink流式计算的新时代。
第二个阶段是2018年到2020年,随着流量的进一步上涨,PyFlink和kafka的性能瓶颈以及当时使用的JSON数据格式带来的性能和数据质量问题纷纷显现出来。与此同时,下流业务对数据延迟、数据质量的敏感程度与日俱增。我们不仅对一些痛点进行了针对性优化,还花费一年多的时间将整个ETL链路从PyFlink切换到Java Flink,使用基于Groovy的规则引擎替换了基于Python的规则引擎,使用Protobuf替代了JSON,新链路相比旧链路性能提升了数倍。同时大数据开发平台和流量平台的建设提升了埋点数据流在任务开发、ETL规则管理、埋点管理、多机房容灾降级等多方面的能力。
第三个阶段是从2021年开始至今,进一步提升数据流ETL链路的性能和稳定性,在满足流量增长和需求增长的同时,降低资源成本和运维成本是这一阶段的主要目标。我们主要从三个方面进行了优化。
-
优化了引擎性能,随着流量和ETL规则的不断增加,我们基于Groovy的规则引擎使用的资源也在不断增加,所以基于Janino对规则引擎进行了重构,引擎的性能得到了十倍的提升。
-
基于流量平台建设了一套比较完善的埋点治理体系,通过埋点下线、埋点管控、埋点采样等手段降低埋点成本。
-
将链路进行了分级,不同的等级的链路保障不同的SLA,在资源不足的情况下,优先保障高优链路。
接下来是我们2018至2020年之间埋点数据流ETL链路建设的一些具体实践。
在介绍业务场景时,提到我们一个主要的需求是ETL规则的动态更新,那么我们来看一下埋点数据流Flink ETL 任务是如何基于规则引擎支持动态更新的,如何在不重启任务的情况下,实时的更新上下游的Schema信息、规则的处理逻辑以及修改路由拓扑。
首先,我们在流量平台上配置了上下游数据集的拓扑关系、Schema和ETL规则,然后通过ConfigCenter将这些元数据发送给Flink ETL Job,每个Flink ETL Job的TaskManager都有一个Meta Updater更新线程,更新线程每分钟通过RPC请求从流量平台拉取并更新相关的元数据,Source operator从MQ Topic中消费到的数据传入ProcessFunction,根据MQ Topic对应的Schema信息反序列化为InputMessage,然后进入到规则引擎中,通过规则索引算法匹配出需要运行的规则,每条规则我们抽象为一个Filter模块和一个Action模块,Fliter和Action都支持UDF,Filter筛选命中后,会通过Action模块对数据进行字段的映射和清洗,然后输出到OutputMessage中,每条规则也指定了对应的下游数据集,路由信息也会一并写出。
当OutputMessage输出到Slink后,Slink根据其中的路由信息将数据发送到SlinkManager管理的不同的Client中,然后由对应的Client发送到下游的MQ中。
规则引擎为埋点数据流ETL链路提供了动态更新规则的能力,而埋点数据流Flink ETL Job使用的规则引擎也经历了从Python到Groovy再到Janino的迭代。
由于Python脚本语言本身的灵活性,基于Python实现动态加载规则比较简单。通过Compile函数可以将一段代码片段编译成字节代码,再通过eval函数进行调用就可以实现。但Python规则引擎存在性能较弱、规则缺乏管理等问题。
迁移到Java Flink后,在流量平台上统一管理运维ETL规则以及schema、数据集等元数据,用户在流量平台编辑相应的ETL规则,从前端发送到后端,经过一系列的校验最终保存为逻辑规则。引擎会将这个逻辑规则编译为实际执行的物理规则,基于Groovy的引擎通过GroovyClassLoader动态加载规则和对应的UDF。虽然Groovy引擎性能比Python引擎提升了多倍,但Groovy本身也存在额外的性能开销,因此我们又借助Janino可以动态高效地编译Java代码直接执行的能力,将Groovy替换成了Janino,同时也将处理Protobuf数据时使用的DynamicMessage替换成了GeneratedMessage,整体性能提升了10倍。
除了规则引擎的迭代,我们在平台侧的测试发布和监控方面也做了很多建设。测试发布环节支持了规则的线下测试,线上调试,以及灰度发布的功能。监控环节支持了字段、规则、任务等不同粒度的异常监控,如规则的流量波动报警、任务的资源报警等。
规则引擎的应用解决了埋点数据流ETL链路如何快速响应业务需求的问题,实现了ETL规则的动态更新,从而修改ETL规则不需要修改代码和重启任务。
但规则引擎本身的迭代、流量增长导致的资源扩容等场景,还是需要升级重启Flink任务,导致下游断流。
除了重启断流外,大任务还可能在重启时遇到启动慢、队列资源不足或者资源碎片导致起不来等情况。
针对这些痛点我们上线了Flink拆分任务,本质上是将一个大任务拆分为一组子任务,每个子任务按比例去消费上游Topic的部分Partition,按相同的逻辑处理后再分别写出到下游Topic。
举个例子:上游Topic有200个Partition,我们在一站式开发平台上去配置Flink拆分任务时只需要指定每个子任务的流量比例,每个子任务就能自动计算出它需要消费的topic partition区间,其余参数也支持按流量比例自动调整。
拆分任务的应用使得数据流除了规则粒度的灰度发布能力之外,还具备了Job粒度的灰度发布能力,升级扩容的时候不会发生断流,上线的风险更可控。同时由于拆分任务的各子任务是独立的,因此单个子任务出现反压、Failover对下游的影响更小。另一个优点是,单个子任务的资源使用量更小,资源可以同时在多个队列进行灵活的部署。
说到ETL链路建设,埋点数据流在容灾与降级能力建设方面也进行了一些实践。
首先是容灾能力的建设,埋点数据流在Flink、MQ、Yarn、HDFS等组件支持多机房容灾的基础上完成了多机房容灾部署,并准备了多种流量调度的预案。
正常情况下流量会均匀打到多个机房,MQ在多个机房间同步,Flink ETL Job默认从本地MQ进行消费,如果某个机房出现故障,我们根据情况可以选择通过配置下发的方式从客户端将流量调度到其他非受灾机房,也可以在CDN侧将流量调度到其他非受灾机房。埋点数据流ETL链路可以分钟级地进入容灾模式,迅速将故障机房的Flink Job切换到可用的机房。
其次是服务降级能力的建设,主要包含服务端降级策略和客户端降级策略。服务端降级策略主要通过LB限流、客户端进行退避重试的机制来实现,客户端降级策略通过配置下发可以降低埋点的上报频率。
举个例子:在春晚活动中参与的用户很多,口播期间更是有着非常巨大的流量洪峰,2021年春晚活动期间为了应对口播期间的流量洪峰,埋点数据流开启了客户端的降级策略,动态降低了一定比例用户的埋点上报频率,在口播期间不上报,口播结束后迅速恢复上报。在降级场景下,下游的指标计算是通过消费未降级用户上报的埋点去估算整体指标。目前我们在此基础上进行了优化,客户端目前的降级策略可以更近一步的根据埋点的分级信息去保障高优的埋点不降级,这样可以在活动场景下保障活动相关的埋点不降级的上报,支持下游指标的准确计算。
下篇主要包含埋点数据流治理实践以及未来规划,敬请关注。
本文为从大数据到人工智能博主「bajiebajie2333」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://lrting.top/backend/7172/