摘要: 本文整理自集度汽车数据部门实时方向负责人、 Apache Flink Contributor 周磊&集度汽车数据开发专家顾云,在 FFA 2022 行业案例专场的分享。本篇内容主要分为四个部分:
1. 集度实时计算发展
2. FlinkSQL 实时入仓实践
3. Flink 计算平台建设
4. 未来规划
2021 年 3 月集度汽车成立。2021 年 11 月 Flink on native k8s 开始搭建。2022 年 4 月,集度汽车第一个实时计算任务上线,是一个小程序埋点实时入仓的 Flink SQL 任务。2022 年 9 月,Flink 计算平台一期正式上线。
那么我们为什么选择 Flink on native k8s 的 Application Mode 呢?
从业务现状和技术现状来讲,我们公司有一个专业的 k8s 运维团队和 Flink 从 0-1 开始建设,没有迁移的成本。从 k8s 本身来讲,k8s 有弹性、故障迁移、资源隔离和易于管理运维等优点。
选择 Native 方式的原因是基于原生的 k8s,HA 不再依赖于外部组件 Zookeeper。选择 Application Mode 的原因是任务级别、资源隔离性更好,不存在资源抢占的情况。
那么选择了 Flink on native k8s 我们需要解决两个关键点。第一个是 Web UI 暴露方式,第二个是日志暴露方式。
第一个关键点 Web UI 的暴露方式有三种,分别是 NodePort、ClusterIP、LoadBalancer。
1. NodePort:暴露一个 Node 的随机端口,提供给外部流量访问 k8s 集群资源。
优点: 启动快,同网络环境可以直接访问。
缺点:网络隔离情况下本地无法访问线上任务的 Web UI;Node 端口数有限制,不能无限扩展。
2. ClusterIP:暴露 Pod IP+Pod Port。
优点:启动快,不额外暴露 Node 端口,更省资源。
缺点:仅在 k8s 集群内部或同网络环境中可以访问,网络隔离情况下本地无法访问线上任务的 Web UI。
优点:配置简单,通过 LB 直接访问线上 Flink 任务的 Web UI。
缺点:任务启动比较慢,因为需要准备相关的 LB 环境;资源消耗大,每个任务都会启动一个 LB;外网访问带来安全问题。
当然一般的云厂商可以支持不启动外网 LB 通过 kubernetes.rest-service.annotations 进行配置。
结合以上三种方式的优缺点,以及我们公司线上线下网络隔离的实际情况,我们最终选择 ClusterIP+Ingress 的方式来访问 Flink 任务 Web UI。
下图是 Ingress 的配置样例。每一个 Flink 任务都配置一个对应的 Ingress 资源,用户通过 host 配置域名进行访问,解析到对应的 Ingress Controller,然后通过 Ingress 配置找到对应 Flink 任务的 rest service 的 8081 端口。这样就实现了通过域名访问线上任务的 Web UI。
第二个关键点日志暴露方式有很多种,比如写本地文件、写 Kafka 以及其他外部存储等。
我们选择的是写本地日志文件,选择这种方式的原因主要是为了与第三方组件解耦,更加的灵活可靠。但是通过日志组件打印的日志文件是在 pod 内部,而 pod 外部无法访问。如果需要在 pod 外部获取,需要将其映射到 Node 的磁盘上。
下图是日志映射的配置文件样例。pod 内部的日志目录为/opt/flink/log 将其映射到 Node 磁盘/data/logs/flinklog 下对应的 Flink 任务名的目录。这样就实现了在同一个目录下,只存在该 Flink 任务的日志文件,更容易进行日志管理。
如图是集度实时数据流架构,数据源分为日志类、DB 类、埋点类、数据类。
其中日志类主要包括 server 端日志、IT 系统日志、安全系统日志、各组件审计日志等。埋点类主要包括云端埋点、APP 小程序、官网、车端埋点。DB 数据指的是后端服务的 binlog 数据。数据类主要包括整车 CAN 信号数据、传感器数据等。
这些数据都流经 Kafka,然后通过 Flink 进行计算后写入下游组件。下游组件主要有 Kafka、HDFS、Hive、Doris,ES 等。
接下来分享一下集度实时入仓的工作原理和架构。在这之前,首先带大家了解一下哪些场景适合使用 Flink SQL 进行实时入仓。
目前集度使用了 Flink SQL 实时入仓的场景主要有日志类数据实时入仓、埋点类数据实时入仓,包括前端埋点和服务端埋点。这一类型的任务没有太过复杂的计算逻辑和额外需要管理的状态,需要快速迭代,比较适合通过 Flink SQL 进行实现。
对于这类场景来讲,经常会有新增埋点字段的需求。使用 SQL 方式将完全规避掉修改代码、重新测试、重新打包的繁琐操作,直接在用户 Flink SQL 部分增加相应的字段即可。
实时入仓主要有三个模块,分别是用户 Flink SQL、Flink SQL 解析引擎、Flink Table Format。用户编写的 Flink SQL 交给 Flink SQL 解析引擎,引擎解析用户 SQL 转换为一个 Flink 任务,然后提交到 k8s 集群。数据的解析逻辑是根据 SQL 中配置的 Format Type,通过 SPI 机制加载对应的 Table Format 工厂类来进行解析的。后续会分别对 Flink SQL 解析引擎、Table Format、用户 SQL 这三部分进行阐述。
第一个是 SQL 解析引擎。主要功能有三个,分别是解析并切分用户 SQL;将 SQL 转换为任务提交至 k8s 运行;Hive Catalog 管理。
就实时入仓场景来讲,对于 Hive 表,我们希望其元数据持久化,由 Hive Metastore 进行管理;而其他表元数据则不希望持久化,仅在 Flink Session 中使用即可。
第二个是 Table Format。在 Flink 1.10 版本及以前,使用 Table Factory 这个工厂类,目前在 1.15 已经是 Deprecated 状态。1.11 版本以后推荐使用 Factory 这个工厂类,目前我们使用的 Flink 版本是 1.13。就以 1.13 为例,来描述一下 Factory 相关的类结构。
Factory 工厂类存在于 flink-table-common 包下,是 Table Source、Sink、Format 的基类。对于 Table Format,我们主要关注五个接口,分别是 Factory,DecodingFormatFactory,EncodingFormatFactory,DeserializationFormatFactory 和 SerializationFormatFactory。如果我们需要对某类数据进行自定义解析,可以实现 DeserializationFormatFactory 遵从 Jave SPI 原则即可。
第三个是埋点入仓的 Flink SQL 样例。可以分为三个部分,Source、Sink 以及 Insert 操作。
1. 第一部分是创建了一个 Hive 的 Sink 表,可以看到通过 Flink Hive 的 Catalog 进行管理,该 Hive 表是一个小时级分区表。分区 Commit 的策略是创建 Succes 文件的同时 Commit 相应的 Hive 分区。
2. 第二部分是 Kafka Source 表,数据解析逻辑,由 Format 的配置项进行配置,其中 Watermark 是通过数据中的 evernt time 进行指定。
3. 第三部分是 Insert 语句,将 Kafka 埋点中对应的字段值写到对应的 Hive 表中,以这样的方式实现了将数据以某种 Format 指定的逻辑进行解析,然后通过实时流的方式写到 Hive 和其他存储中。
在今年 4 月份我们在提交了第一个 Flink on native k8s 任务后,后续各个业务方向都想复用 Flink 实时计算的能力。比如以下三个场景:
第一个是基础的实时数据传输场景,业务方希望将业务库的数据便捷的分发到多种存储引擎中应对不同的需求。第二个是数据分析和大屏的场景,分发用户在 APP 上的各种埋点数据来供后续的计算。第三个是车端的监控和挖掘场景,接入车端的埋点数据和信号数据后,构建计算和存储链路。
在初始的开发阶段,我们面临多个开发痛点,比如每个用户都需要手工维护自己提交的 Flink 任务,包括资源版本、配置、历史提交等等。
举一个任务升级场景的例子,我们需要手工进行资源更新、编译打包、编辑提交命令。资源由于没有统一存储的地方很容易搞混,导致线上的版本不是最后升级的版本。
从开发角度来看,每个开发同学都需要了解 Data Stream API 和任务中每个配置的具体意义。对于不熟悉 Flink 的人来说,上手成本比较高。从任务维护角度来看,Flink 任务提交后缺少统一的日志与指标收集,开发人员只能在任务失败退出后,才能收到报警信息,且在失败后想拉取日志、定位问题,目前也没有统一的日志搜索和下载的入口。
从集群维护角度来看,我们还碰到了由于用户不了解某些 Flink 原理,导致集群资源占满,使其他任务一直处于资源申请状态。或是多个用户更改同一个配置文件后,提交的任务没有按照预期运行等等。比如经典的数据入仓场景,由于其他的用户更改了 checkpoint 的配置,导致数据一直落不了仓。
基于以上的问题,我们在 5 月份正式立项,开始建设集度内部 Flink 计算平台。目前集度的 Flink 计算平台已经上线三大功能模块,分别是服务管理、运维管理、资源管理。
1. 多版本的资源管理:用户可以自由切换资源版本。
2. 作业生命周期管理:作业从创建到结束的所有状态变化都由平台来维护。
3. 作业可配置参数管理:官方参数和平台特有的定制化参数。
4. Flink 引擎多版本管理:根据用户的具体需求,提供多版本的选择,目前默认版本为 Flink 1.13。
运维管理层面,提供了丰富的任务指标看板,并基于这些指标定制化监控报警的功能,解决了上述所说的 Flink 任务黑盒问题。同时,为了便于用户追溯与定位问题,建设了任务提交批次的概念,收集任务分批次日志。
资源管理层面,会管理每个任务提交所需的 CPU 和 memory 资源,防止每个任务无上限的申请资源,并对集群的资源进行监控。一旦有大规模资源 pending 的情况,快速介入运维解决。
下图展示了我们当前 Flink 计算平台的整体架构,主要分成三个部分。
第一部分是我们的平台服务。目前我们的计算平台分布在 k8s 的服务集群上,统一走公司的服务注册,复用已有的能力,比如服务发布,域名管理,监控报警等等。
第二部分是我们所有 Flink 任务运行的 k8s 集群。这个集群目前由我们和运维团队一起维护,里面的 k8s 资源由 Flink 计算平台维护,子网地址等其他外部云服务资源由基础运维团队维护。
第三部分是我们依赖的一些基础组件。比如利用公司的持续集成 CICD 来构建 docker 镜像;日志采集工具用来收集每个 K8s Node 上的日志;搜索引擎 ES 用来搜索近期的 Flink 日志;HDFS 用来存储历史所有的 Flink 日志。
以一个 Flink Jar 包任务为例,来看一下整体 Flink 计算平台的处理流程。首先是任务提交时选择的资源版本,因为用的是 Flink on native k8s 资源统一打包成 docker 镜像。我们提供了两种打包方式,主动上传和自动触发。
主动上传是指,用户在上传完成后可以选择自己上传的版本,来生成对应版本的镜像,我们的镜像管理服务可以将任务资源生成的各版本镜像,上传到公司自建的 docker 仓库中。
自动触发的话,我们会打通公司的 CICD 为每个 Flink Git 项目的变更提交产生一个新的镜像。镜像生成的时候会根据用户的配置来加载对应的 Flink 引擎版本,以及会从 HDFS 上拉取对应的依赖资源 Jar 包。
在镜像生成后的任务提交阶段,我们会针对每个作业定制化日志映射配置和环境变量,来打通后面的批次日志采集流程。这些配置都会应用在每个任务的 k8s 资源上。
任务提交后,我们会利用 k8s 的 watch and informer 机制监听每个任务所有资源信息的变化事件,以及获取到最新的 Flink 任务信息后,来推动每个任务的状态流转。
在任务运行阶段,我们提供了三个任务运行状态查看的方式。
1. 用户可以通过域名访问 Flink Web UI。其原理主要是通过创建的 Ingress 资源来二次反向代理到任务的 rest service。
2. 用户可以通过 grafana 来查看每个任务的可视化指标,Prometheus 会收集每个任务的运行指标。
3. 用户可以查看当前运行的日志和历史批次日志。历史批次日志是日志映射到 K8s Node 后,通过 Flume 收集到 Kafka,统一格式解析后流入 ES 和 HDFS,由统一的搜索接口供用户使用。
而实时运行日志是通过 k8s 的 log watch 方式来增量获取实时运行日志的。
下图是我们 Flink 计算平台的页面展示,可以看到平台上每个作业的元数据信息和当前作业的状态信息等等。目前平台管理了 100+的实时任务,接入的业务方包括数仓团队、实时数据开发团队、车云链路团队。
下面展示的是我们 Flink 计算平台在任务提交后的任务状态流转图。一共列举了九个状态,接下来分别来讲一下每个状态的意义。started 是指任务成功提交后的初始状态。jm pod running 是指 jm pod 资源申请成功状态。任务在 started 状态下,如果申请到 jm 的 pod 资源,会在 pod 正常运行后流转到该状态。
pod running 是指任务所有集群资源都申请成功的状态,任务在 jm pod running 状态下,如果申请到所有 tm 的 pod 资源,会在 tm pod 正常运行后流转到该状态。running 是指 Flink 任务运行态,收到 Flink 任务 running 状态信息后流转到该状态。
not running 是指 Flink 任务非运行状态,比如 tm 或者 jm 重启,收到 Flink 任务非 running 状态的信息后,流转到该状态。
success 是指任务成功状态。stopping 是指任务停止中间状态。前置状态可以是多个状态,如果用户执行了停止操作,任务将流转到该中间状态。
stopped 是指停止状态,任务在 stopping 状态下,如果收到资源确认、删除信息以后会流转到该状态。Failed 是指任务失败状态,任务在多个状态下都可以流转到该状态。
在未来的一年中,我们将使用 Flink 更好地支撑公司的需求,会继续在平台建设的迭代和湖仓一体的建设进行探索。
计算平台是实时业务的技术底层,也是 Flink 面向用户的唯一渠道,我们将从三个方 向不断增强功能,提升用户体验和效率。
1. 投入更多精力在 Flink SQL 的平台化上,进一步降低用户使用门槛。比如 SQL 语法校验、SQL 调试、统一管理元数据等等。
2. 尝试实现资源的动态扩缩容。实现平台自动化调整 Flink 作业资源,解决某些场景下业务数据增长带来的问题。
3. 稳定性建设和性能建设。比如作业在流量高峰如何保持稳定的性能;生产上会持续产生文件的情况下,作业输出的文件如何进行调优等。
在湖仓一体方面,很多业务本质上还处于起始发展阶段,我们会从一个新的业务方向落地一个湖仓一体的解决方案,慢慢的去探索和优化。在计算侧我们主要会放在统一的数据模型、统一的 UDF、CDC 数据入湖,在存储侧我们将会探索一个统一的存储引擎。
本文转载自周磊&顾云@集度 Apache Flink ,原文链接:https://mp.weixin.qq.com/s/Ywxw6Jt9D4Rh4sXD1yirNQ。