写点什么

Flink 在字节跳动数据流的实践

  • 2022 年 1 月 11 日
  • 本文字数:6833 字

    阅读完需:约 22 分钟

Flink 在字节跳动数据流的实践

本文是字节跳动数据平台开发套件团队在 1 月 9 日 Flink Forward Asia 2021: Flink Forward 峰会上的演讲分享,将着重分享 Flink 在字节跳动数据流的实践。

字节跳动数据流的业务背景

数据流处理的主要是埋点日志。埋点,也叫 Event Tracking,是数据和业务之间的桥梁,是数据分析、推荐、运营的基石。


用户在使用 App、小程序、Web 等各种线上应用时产生的行为,主要通过埋点的形式进行采集上报,按不同的来源分为客户端埋点、Web 端埋点、服务端埋点。


不同来源的埋点都通过数据流的日志采集服务接收到 MQ,然后经过一系列的 Flink 实时 ETL 对埋点进行数据标准化、数据清洗、实时风控反作弊等处理,最终分发到下游,主要的下游包括 ABTest、推荐、行为分析系统、实时数仓、离线数仓。


所以,如果用一句话来概括数据流主要业务,其实就是埋点的收集、清洗、分发。


目前在字节跳动,清洗和分发环节是基于 Flink 搭建的。

​01 - 数据流业务规模

  • 业务数量:在 字节跳动,包括抖音、今日头条、西瓜视频、番茄小说在内的 3000 多个大大小小的 APP 和服务都接入了数据流。

  • 数据流峰值流量:当前,字节跳动埋点数据流峰值流量超过 1 亿每秒,每天处理超过万亿量级埋点,PB 级数据存储增量。

  • ETL 任务规模:目前,字节跳动数据流在多个机房部署超过 1000 个 Flink 任务超过 1000 个 MQ Topic,使用超过 50W Core CPU单任务最大 12W Core CPU ,Topic 最大 10000 Partitio。

02 - 数据流业务挑战

字节跳动数据流 ETL 遇到的挑战主要有四点:


  • 第一点流量大,任务规模大

  • 第二点,处在所有产品数据链路最上游,下游业务多,ETL 需求变化频繁

  • 第三点高 SLA 要求,下游推荐、实时数仓等业务对稳定性和时效性有比较高的要求。

  • 最后一点,在流量大、业务多、SLA 要求高的情况下,针对流量、成本、SLA 保障等多维度的综合治理也面临挑战。


下面从两个数据流业务场景中介绍一下我们遇到的业务挑战。

1、UserAction ETL 场景

在 UserAction ETL 场景中,我们遇到的核心需求是:种类繁多且流量巨大的客户端埋点需求和 ETL 规则动态更新的需求。


在字节内部,客户端的埋点种类繁多且流量巨大,而推荐关注的只是部分埋点,因此为了提升下游推荐系统处理效率,会在数据流配置一些 ETL 规则,对埋点进行过滤,并对字段进行删减、映射、标准化之类的清洗处理,将埋点打上不同的动作类型标识。


处理之后的埋点一般称之为 UserAction,UserAction 数据会和服务端展现等数据在推荐 Joiner 任务的分钟级窗口中进行拼接 Join,产出 Instance 训练样本。


举个例子:一个客户端的文章点赞埋点描述了用户在一个时间点对某一篇文章进行了点赞操作,埋点经过数据流日志采集服务进入数据流 ETL 链路,通过 UserAction ETL 处理后实时地进入到推荐 Joiner 任务中拼接生成样本更新推荐模型,从而提升用户体验。

如果产出 UserAction 数据的 ETL 链路出现比较大的延迟,那么就不能在窗口内及时完成拼接,可能导致用户体验下降。


因此对于推荐来说,数据流的时效性是一个强需求


而推荐模型的迭代、产品埋点的变动都可能导致 UserAction 的 ETL 规则的变动。如果 ETL 规则硬编码在代码中,每次修改都需要升级代码并重启 Flink Job,会影响数据流稳定性和数据的时效性。因此,这个场景的另一个需求就是 ETL 规则的动态更新

2、数据分流场景

目前,抖音业务的埋点 Topic 晚高峰流量超过 1 亿/秒,而下游电商、直播、短视频等不同业务的实时数仓关注的埋点范围实际上都只是其中的一小部分。


如果各业务分别使用一个 Flink 任务,消费抖音埋点 Topic,过滤消费各自关注的埋点,需要消耗大量 Yarn 资源,同时会造成 MQ 集群带宽扇出严重,影响 MQ 集群的稳定性。


因此,数据流提供了数据分流服务,使用一个 Flink 任务消费上游埋点 Topic,然后通过配置规则的方式,将各业务关注的埋点分流到下游小 Topic 中,再提供给各个业务消费。这样就减少了不必要的反序列化开销,同时降低了 MQ 集群带宽扇出比例。


在数据分流场景中,核心需要解决的是高稳定的 SLA。因为断流、数据延迟可能会影响推荐效果、广告收入、实时数据报表。


​同时随着业务发展,实时数据需求日益增加,分流规则新增和修改也会日益频繁。如果每次规则变动都需要修改代码并重启 Flink Job,会影响很多下游,因此分流规则的动态更新也是这一场景中的强需求。

字节跳动数据流实践

01-数据流 ETL 链路建设

字节跳动数据流 ETL 链路建设主要经历了三个阶段:


  1. 第一阶段是 2018 年以前业务需求快速迭代的早期阶段


主要使用 PyJStorm 和基于 Python 的规则引擎构建主要的流式数据处理链路。其特点是比较灵活,可以快速支持业务需求。


但随着埋点流量快速上涨,PyJStorm 暴露出很多稳定性和运维上的问题,性能也不足以支撑业务的增长。

2018 年,公司内部开始大力推广 Flink,并且针对大量旧任务使用 PyJStorm 的情况,提供了 PyJStorm 到 PyFlink 的兼容适配。流式任务托管平台的建设一定程度上解决了流式任务运维管理的问题。数据流 ETL 链路也在 2018 年全面迁移到了 PyFlink,进入了流式计算的新时代。


  1. 第二个阶段是 2018 至 2020 年

随着流量的进一步上涨,PyFlink 和 Kafka 的性能瓶颈、以及 JSON 数据格式带来的性能和数据质量问题都一一显现出来,与此同时下游业务对延迟、数据质量的敏感程度却是与日俱增。


于是,我们一方面对一些痛点进行了针对性的优化。另一方面,花费 1 年多的时间将整个 ETL 链路从 PyFlink 切换到了 Java Flink,使用基于 Groovy 的规则引擎替换了基于 Python 的规则引擎,使用 ProtoBuf 替换了 JSON。


数据流 ETL 新链路,相比旧链路性能提升了 1 倍


与此同时,一站式大数据开发平台和流量平台的建设提升了数据流在任务开发运维、ETL 规则管理、埋点元数据管理、多机房容灾降级等多方面的能力。


  1. 第三个阶段是从 2021 年开始

在全球资源供应紧张的背景下,进一步提升数据流 ETL 性能和稳定性,满足流量增长和需求增长的同时,降低资源成本和运维成本,是这一阶段的主要目标。我们主要从三个方面进行了优化:


  • 优化引擎性能。随着流量和 ETL 规则的不断增加,基于 Groovy 的规则引擎使用的资源也不断增加,于是我们基于 Janino 进行了重构,引擎性能得到数倍提升。


  • 优化埋点治理体系。我们基于流量平台建设了一套比较完善的埋点治理体系,通过无用埋点下线、埋点采样等手段降低埋点成本。


  • 优化链路。我们进行了链路分级,不同等级的链路保障不同的 SLA,在资源不足的情况下优先保障高优埋点链路。


从 2018 年到 2020 年,我们持续在数据流 Flink ETL Job 应对需求挑战上取得了一些实践效果。下图展示了数据流 Flink ETL Job 是如何支持动态更新的,在不重启任务的情况下,实时更新上下游 Schema、规则处理逻辑、修改路由拓扑。


流量平台 Config Center 为数据流 Flink ETL Job 提供上下游数据集拓扑关系、Schema、ETL 规则和 UDF 等元数据。

数据流 Flink ETL Job 中的每个 TaskManager 中会有一个 Meta Updater 更新线程,更新线程每分钟通过 RPC 请求从流量平台拉取并更新相关元数据。


Source 将从 MQ 中消费到的数据传入 ProcessFunction,根据 MQ 对应的 Schema 反序列化为 InputMessage,然后进入规则引擎中,通过规则索引匹配出需要运行的规则,每条规则抽象为一个 Filter 模块和一个 action 模块,Filter 和 action 都支持 UDF ,Filter 筛选命中后,通过 action 模块对输入数据进行字段映射和清洗,然后写出到 OutputMessage 中。


每条规则也指定了对应的下游数据集,路由信息也会一并写出到 OutputMessage。OutputMessage 输出到 Sink 后,Sink 根据 OutputMessage 中的路由信息将数据发送到 SinkManager 管理的 Client,由对应的 Client 发送到下游 MQ。


这里解释一下我们为什么让每个 TaskManager 通过一个 MetaData updater 定时去更新元数据,而不是通过增加一条元数据流来更新。这么做的原因主要是因为使用元数据流更新的方式需要开启 Checkpoint 以保存元数据的状态,而在字节跳动数据流这样的大流量场景下,开启 Checkpoint 会导致在 Failover 时产生大量重复数据,下游无法接受。


1、规则引擎的解决方案

数据流 Flink ETL Job 使用的规则引擎经历了从 Python 到 Groovy 再到 Janino 的迭代。规则引擎对于数据流来说最主要的就是提供动态更新 ETL 规则的能力。


Python 由于脚本语言本身的灵活性,动态加载规则实现起来比较简单,通过 Compile 函数可以将一段规则代码片段编译成字节代码,再通过 eval 函数进行调用即可。但存在性能较低,规则缺乏管理的问题。


迁移到 Java Flink 后,我们在流量平台上统一管理 ETL 规则、Schema、数据集等元数据。用户在流量平台编辑 ETL 规则,规则从前端视图发送到后端,经过一系列校验后保存为逻辑规则,引擎将逻辑规则编译为物理规则运行。Groovy 本身兼容 Java,所以我们可以通过 GroovyClassLoader 动态的加载规则、UDF。


但使用 Groovy,虽然性能比 Python 提高了很多倍,但额外的开销仍比较大,因此我们又借助 Janino 可以高效动态编译 Java 类并加载到 JVM 直接执行的能力,将 Groovy 替换为 Janino。


除了规则引擎的迭代,我们在平台侧的测试、发布、监控和报警方面也做了很多建设。


测试发布环节支持了规则的线下测试、线上调试、灰度发布等功能,监控环节则是支持字段、规则、任务等不同粒度的异常监控,并支持了规则流量的波动报警、任务的资源报警等功能。


规则引擎的应用解决了数据流 ETL 链路如何快速响应业务需求的问题,实现了动态调整 ETL 规则不需要修改代码、重启任务。


但规则引擎本身的迭代、流量增长导致的资源扩容等场景还是需要升级重启 Flink 任务,引发断流。除了重启断流外,大任务还可能遇到启动慢、队列资源不足或资源碎片导致起不来等问题。

2、Flink 拆分任务的实践

针对这些痛点,我们上线了 Flink 拆分任务。Flink 拆分任务本质上就是将一个大任务拆分为一组子任务,每个子任务按比例消费上游 Topic 一部分 Partition,处理后再分别写出到下游 Topic。



举个例子,上游 Topic 有 200 个 Partition,我们在大数据研发治理套件 DataLeap 的数据开发上配置一个 Flink 拆分任务只需要指定每个子任务的流量比例,其余参数都可以按比例自动同步。


拆分任务的应用使得数据流 Flink ETL Job 除了规则粒度的灰度发布能力,还具备了 Job 粒度的灰度发布能力,从此升级、扩容不断流,上线风险更可控。同时,由于拆分任务各子任务是独立的,因此单个子任务出现反压、fail-over 不会影响其他子任务,对下游的影响更小。另外一个优点是单个子任务资源使用量更小,子任务可以同时在多个队列灵活部署。


在流量迅速增长的阶段,数据流最开始是通过 Kafka Connector 直接写 Kafka。但是由于数据流 Flink ETL Job 任务处理的流量大,Sink 比较多,批量发送的效率不高,Kafka 集群写入请求量很大,另外由于每个 Sink 一个 Client,Client 与 Kafka 集群间建立的连接数很多,而 Kafka 集群由于 Controller 性能瓶颈也无法继续扩容。


为了缓解 Kafka 集群压力,数据流 Flink ETL Job 引入了 Databus 组件。


Databus 以 Agent 的方式部署 Yarn 节点上,Agent 中每个 Channel 对应一个 Kafka Topic。数据流 FlinkETL Job 每个 TM 中的 SinkManager 使用 Databus Client 通过 Unix Domain Socket 的方式将数据发送到 Databus Agent 的 Channel 中,再由 Channel 将数据批量发送到对应的 Kafka Topic。


由于每个 Yarn 节点上所有的 TM 都先把数据发送到本机的 Databus Agent,每个 Databus channel 聚合了机器上所有 TM Sink 写同一个 Topic 的数据,因此批量发送的效率非常高,极大的降低了 Kafka 集群的写入请求量,与 Kafka 集群之间需要建立的连接也更少。


同时,单个请求中数据条数的增加带来更高的压缩效率,在 Databus  Agent 上开启了 ZSTD 压缩后,Kafka 集群写入带宽降低了 37%,极大的缓解了 Kafka 集群的压力。

春晚活动是万众瞩目的一大盛事,2021 年春晚活动期间数据流对相关的埋点链路进行了重点保障。


首先是完成了多机房的容灾部署并准备了多种切流预案,正常情况下流量会均匀的打到多个机房,MQ 多机房同步,Flink ETL Job 都从本地消费。如果某个机房出现网络或其他大规模故障,可以从客户端将流量调度到其他机房,也可以在 CDN 侧将流量调度到不同的机房,数据流 Flink ETL 链路可以分钟级进入容灾模式,切换到可用机房。


为了应对口播期间的流量洪峰,我们还准备了客户端降级策略与服务端降级策略。其中客户端降级策略可以动态的降低一定百分比用户的埋点上报频率,口播期间不上报,口播结束后逐步恢复。


在降级场景下,下游指标计算可以通过消费未降级的活动埋点分流估算整体指标。春节活动链路的顺利保障标志着数据流基于 Flink 搭建的 ETL 链路已经能提供较好的稳定性和可用性。

02 - 数据流治理实践

数据流比较常见的治理问题包括但不限于以下几个:


  • 第一个是数据流稳定性治理中最常见的一个问题——Yarn 单机问题导致 Flink 任务 fail-over、反压、消费能力下降。Yarn 单机问题的类型有很多,比如:队列负载不均、单机 load 高、其他进程导致 CPU 负载高、硬件故障等等。

  • 第二个问题是 Kafka 集群负载不均导致 Flink 任务生产消费受到影响

  • 第三个问题是埋点治理场景中无用埋点、异常埋点消耗大量计算存储资源


针对单机问题,我们从 Flink 和 Yarn 两个层面分别进行了优化,将单机 load 高导致的延迟减少了 80%以上。


  • 首先,Flink 层面的优化。

在数据流 ETL 场景中,为了减少不必要的网络传输,Partitioner 主要采用 Rescale Partitioner。而 Rescale Partitioner 会使用 Round-robin 的方式发送数据到下游部分 Channel 中,由于单机问题可能出现个别任务处理能力不足的情况,导致反压,任务出现 lag。


实际上数据发到下游任何一个任务都是可以的,最合理的策略应该根据下游任务的处理能力去发送数据

另一方面,我们注意到 Flink Credit-based Flow Control 反压机制中,可以通过 Backlog Size 判断下游任务的处理负载,那么我们就可以将 Round-robin 发送的方式修改为根据 Channel 的 Backlog Size 信息选择负载更低的下游 Channel 发送的方式。


方案上线后队列的负载更加均衡,CPU 利用率提升 10%。

  • 其次,Yarn 层面的优化。

第一、队列资源使用独立 Label 队列,避免高峰期和其他低优任务互相影响;

第二、Yarn 节点上的 DataNode 偶发有带宽打满、CPU 使用高的情况,影响节点上数据流 Flink ETL 任务的稳定性,通过给 DataNode 设置网络限速并进行 CPU 绑核以避免 DataNode 对 Flink 进程的影响;


第三、Yarn 反调度策略。目前字节跳动 Flink 使用的 Yarn GangScheduler 调度策略会根据约束条件选择性的获取分配到的 Yarn 资源,在任务启动时做到比较均衡的放置 Container,但由于时间的推移,流量的变化等诸多因素,队列还是可能会出现负载不均衡的情况。


反调度策略则是为了解决负载不均衡而生的二次调度机制。Yarn 会定期检查集群中不再满足原有约束的 Container,并在这些 Container 所在的节点上筛选出需要重新调度的 Container 返回给 Flink JobManager,Flink 会重新调度这些 Container。


重新调度会按照原有约束尝试申请等量的可用资源,申请成功后进行迁移,申请不成功不做操作。


针对 Kafka 集群优化问题,我们自研来了存储计算分离的 MQ——BMQ,单 GB 流量成本下降 50%。

在数据流这种大流量场景下使用 Kafka,经常会遇到 broker 或者磁盘负载不均衡、磁盘坏掉等情况,进行扩容、机器替换时的运维操作会引起集群 Under Replica, 影响读写性能。除此之外,Kafka 还有集群规模瓶颈、多机房容灾部署成本高等缺点。


为了优化这些问题,BMQ 这款字节跳动自研的存储计算分离的 MQ 应运而生。


BMQ 数据使用 HDFS 分布式存储,每个 partition 被切分为多个 segment,每个 segment 对应一个 HDFS 文件,元数据使用 kv 存储,Proxy 和 Broker 都是无状态的,因此可以支持快速扩缩容,且没有数据拷贝不会影响读写性能。受益于 HDFS 多机房容灾部署能力,BMQ 多机房容灾部署变得比较简单,数据同时写入所有容灾机房成功后才会向 client 返回成功,数据消费则是在每个机房本地消费,减少了跨机房带宽,除此之外,由于基于 HDFS 存储所需的副本数更少,单 GB 流量成本下降 50%

针对埋点治理,我们从全产品开启埋点管控、无用埋点监控 &自助下线、埋点分级、风控能力建设四个点入手。


  • 第一点,全产品开启埋点管控。所有产品都需要先在流量平台注册埋点元数据才能上报,这是从埋点接入流程进行的治理。

  • 第二点,对于已上报的埋点,我们会通过埋点血缘,统计出已经没有在使用的埋点,自动通知埋点负责人在平台进行自助下线。埋点注册和埋点下线完成后,都会通过埋点管控服务动态下发相关的配置信息到埋点 SDK 和数据流 Flink ETL 任务中,从而保障未注册埋点和无用埋点在上报或 ETL 环节被丢弃掉。

  • 第三点是根据不同的用途对埋点进行分级,从而 Dump 到 HDFS 和数仓的时候可以按不同等级进行分区,不同等级的分区提供不同的 TTL 和就绪时间的保障。

  • 最后一点则是针对异常流量,数据流 ETL 链路接入了风控系统,对埋点进行实时打标或过滤,防止异常流量造成数据倾斜、数据延迟、统计指标异常等问题。

目前,Flink 在字节跳动数据流实践中,已经可以做到计算层面的流批一体。接下来,我们还将计划探索计算和存储的流批一体,同时也会探索云原生架构,实现资源的动态 Rescale,提升资源利用率。我们也会一些高优链路保障上追求更高的 SLA,比如保障端到端 Exactly-once 语义。


目前,现有的能力已经通过火山引擎大数据研发治理套件 DataLeap 对外开放。

欢迎关注字节跳动数据平台同名公众号

发布于: 刚刚阅读数: 2
用户头像

公众号byte-dataplatform 2021.12.29 加入

字节跳动数据平台团队,赋能字节跳动各业务线,对内支持字节绝大多数业务线,对外发布了火山引擎品牌下的数据智能产品,服务行业企业客户。同名公众号欢迎了解。

评论

发布
暂无评论
Flink 在字节跳动数据流的实践