大数据工程之Flink架构浅析

293 阅读12分钟

背景

  • 身在大数据工程团队,经常同ODPS&D2&Flink打交道,了解掌握大数据相关知识对工程端还是有必要;早期团队技术之美围绕Flink专题学习了论文《Apache Flink™: Stream and Batch Processing in a Single Engine》,此后在两年的时间断断续续里消化理解该论文;通过网上资料进行了更深入的了解,这里将学习到的内容记录下来。

什么是大数据工程

说起大数据工程,大数据到底是从什么时候火起来的?这个问题并没有一个确切的答案,如果一定要说,2010 年算是初现苗头。但是,作为大数据时代开拓者的谷歌,它的“三架马车”远远早于 2010 年诞生:谷歌文件系统第一次公开发表的论文是在 2003 年,MapReduce 公开发表的时间是 2004 年,而 BigTable 则公开发表于 2006 年

Google的三篇论文一直是大数据时代的经典代表,经过十几年的演进进化,根据三篇论文有了我们日常工作中经常接触的大数据存储知识。

  • 根据MapReduce,于是我们有了Hadoop;
  • 根据BigTable,于是我们有了HBase。
  • 根据GFS,于是我们有了HDFS;

在大数据打交道的业务场景中,除了TP场景的关系型数据MySQL外,更多的是AP场景的海量大数据的处理和计算

  • NoSQL:Hbase&Lindorm是典型代表;面向列式存储的稀疏矩阵结构,海量大数据点查场景首选存储选型,能支持千万级高并发查询,读写性能毫秒级别

  • 分析型数据库:基于Lindorm数据分析能力局限性,业界衍生出一批AP场景的OLAP在线分析性数据库,基于MPP架构的火山模型和向量化的执行引擎,能较好的满足分析师对海量数据在线OLAP分析,阿里目前常用主打选型集团Hologres,号称能做到

  • 点查和Hbase一样快

  • 复杂OLAP查询比clickHouse、presto、impala快

  • 计算引擎:计算引擎的时代,从Hadoop离线计算到Flink实时流的计算,数据产出从T+1天离线天级别到实时秒级别,具有划时代的意义,由此展开了本篇学习的Flink计算引擎

Flink前世今生

Apache三代计算引擎

讲FLink之前,对三代计算引擎简单进行个对比分析

Hadoop

  • 架构:MapReduce分布式计算 + HDFS分布式文件

  • 并发行高:能同时运行很多任务

  • 基于Map + Reduce过程会有多次落盘,数据产出时效性不高,只能用于离线数据产出

  • 底层API编程,早期对只会SQL的分析师不友好

  • 当时使用Hadoop进行数据分析的大部分是数据科学家和分析师,其中分析师占更多数。分析师更习惯借助各种BI工具,使用SQL来进行自助分析,编程并不是他们技能包的一部分,因此他们对SQL on Hadoop的呼声一直很高,于是有了Hive第一款产品,集团离线D2上ODPS类似

Spark

详细可参考:Spark—RDD架构浅析

  • 运算快:弹性分布式数据集RDD + 内存存储,所以需要搭配存储(常用HDFS)
  • 基于checkpoint的备份机制,容错机制比Hadoop方式科学,恢复数据不用重新计算
  • 伪实时,使用微批式处理实时计算(有界数据流),延时较高(0.5~2s)

FLink

基于Spark处理实时的延时问题,apache Flink横空出时:基于事件驱动, 面向流处理架构

  • 真正流式计算,无界数据流
  • 事件驱动方式,基于event + window 窗口计算方式
  • 基于checkpoint + state机制,不用在STW的情况下快速恢复failover job
  • 唯一缺陷:流批一体局限性(apache paimon 致力解决离线实时技术壁垒)

Flink简介

Flink VS Spark

Flink和Spark二者的区别在于无限数据流的数据会随时间的推演而持续增加,计算持续进行且不存在结束的状态,相对的有限数据流数据大小固定,计算最终会完成并处于结束的状态。

在 Spark 的世界观中,一切都是由批次组成的,离线数据是一个大批次,而实时数据是由一个一个无限的小批次组成的。 --流是批的特例

而在 Flink的世界观中,一切都是由流组成的,离线数据是有界限的流,实时数据是一个没有界限的流,这就是所谓的有界流和无界流。 --批是流的特例

Flink常用场景

Flink 常见的三种场景:事件驱动应用、实时ETL和数据流、实时数据分析

事件驱动应用

在触发某些规则后,Data Driven 会进行处理或者是进行预警,这些预警会发到下游产生业务通知,这是Data Driven 的应用场景,Data Driven 在应用上更多应用于复杂事件的处理。

实时推荐(例如在客户浏览商家页面的同时进行商品推荐)

模式识别或复杂事件处理(例如根据信用卡交易记录进行欺诈识别)

异常检测(例如计算机网络入侵检测)

实时数据分析

  • 移动应用中的用户行为分析
  • 消费者技术中的实时数据即席查询

实时ETL和数据流

  • 实时数仓

当下游要构建实时数仓时,上游则可能需要实时的Stream ETL。这个过程会进行实时清洗或扩展数据,清洗完成后写入到下游的实时数仓的整个链路中,可保证数据查询的时效性,形成实时数据采集、实时数据处理以及下游的实时Query。

  • 搜索引擎推荐

搜索引擎这块以淘宝为例,当卖家上线新商品时,后台会实时产生消息流,该消息流经过Flink 系统时会进行数据的处理、扩展。然后将处理及扩展后的数据生成实时索引,写入到搜索引擎中。这样当淘宝卖家上线新商品时,能在秒级或者分钟级实现搜索引擎的搜索。

Flink架构

相关概念

讲Flink架构前,介绍几个Flink内部概念,一个完整的Blink任务主要有三部分组成:

  • 数据源(INPUT) - Flink外部数据源表 --Source
  • 业务逻辑(QUERY) - Flink内部处理逻辑 --Operator
  • 结果表(OUTPUT),如下:Flink外部结果表 --Sink

一个Flink Job先是由外部输入经过Source算子转换为Source,然后在数据流中经过一系列的算子运算,再由Sink算子转换为输出流,流入外部结果表。

集群启动架构

当 Flink集群启动后,首先会启动一个 JobManger和一个或多个的 TaskManager。由 Client 提交任务给 JobManager,JobManager再调度任务到各个 TaskManager去执行,然后 TaskManager将心跳和统计信息汇报给 JobManager。TaskManager之间以流的形式进行数据的传输。上述三者均为独立的 JVM 进程。

•Client 为提交 Job 的客户端,可以是运行在任何机器上(与 JobManager环境连通即可)。提交 Job 后,Client 可以结束进程(Streaming的任务),也可以不结束并等待结果返回。

•JobManager 主要负责调度 Job 并协调 Task 做 checkpoint,职责上很像 Storm 的 Nimbus。从 Client 处接收到 Job 和 JAR 包等资源后,会生成优化后的执行计划,并以 Task 的单元调度到各个 TaskManager去执行。

•TaskManager 在启动的时候就设置好了槽位数(Slot),每个 slot 能启动一个 Task,Task 为线程。从 JobManager处接收需要部署的 Task,部署启动后,与自己的上游建立 Netty连接,接收数据并处理。

软件架构

API & Libraries 层

这一层主要提供了编程 API 和 顶层类库:

编程 API : 用于进行流处理的 DataStream API 和用于进行批处理的 DataSet API;

顶层类库:包括用于复杂事件处理的 CEP 库;用于结构化数据查询的 SQL & Table 库,以及基于批处理的机器学习库 FlinkML和 图形处理库 Gelly。

Runtime 核心层

这一层是 Flink分布式计算框架的核心实现层,包括作业转换,任务调度,资源分配,任务执行等功能,基于这一层的实现,可以在流式引擎下同时运行流处理程序和批处理程序。

Runtime的输入是物理执行计划,输出是最终结果。涉及代码生成、向量化等技术

物理部署层

Flink的物理部署层,用于支持在不同平台上部署运行 Flink应用。

Flink执行拓扑Graph

Flink 中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图。

  • **StreamGraph:**是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。
  • **JobGraph:**StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。主要的优化为,将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。
  • **ExecutionGraph:**JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。
  • **物理执行图:**JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。

  • **StreamGraph:**根据用户通过 Stream API 编写的代码生成的最初的图。
    • StreamNode:用来代表 operator 的类,并具有所有相关的属性,如并发度、入边和出边等。
    • StreamEdge:表示连接两个StreamNode的边。
  • **JobGraph:**StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。
    • JobVertex:经过优化后符合条件的多个StreamNode可能会chain在一起生成一个JobVertex,即一个JobVertex包含一个或多个operator,JobVertex的输入是JobEdge,输出是IntermediateDataSet。
    • IntermediateDataSet:表示JobVertex的输出,即经过operator处理产生的数据集。producer是JobVertex,consumer是JobEdge。
    • JobEdge:代表了job graph中的一条数据传输通道。source 是 IntermediateDataSet,target 是 JobVertex。即数据通过JobEdge由IntermediateDataSet传递给目标JobVertex。
  • **ExecutionGraph:**JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。
    • ExecutionJobVertex:和JobGraph中的JobVertex一一对应。每一个ExecutionJobVertex都有和并发度一样多的 ExecutionVertex。
    • ExecutionVertex:表示ExecutionJobVertex的其中一个并发子任务,输入是ExecutionEdge,输出是IntermediateResultPartition。
    • IntermediateResult:和JobGraph中的IntermediateDataSet一一对应。一个IntermediateResult包含多个IntermediateResultPartition,其个数等于该operator的并发度。
    • IntermediateResultPartition:表示ExecutionVertex的一个输出分区,producer是ExecutionVertex,consumer是若干个ExecutionEdge。
    • ExecutionEdge:表示ExecutionVertex的输入,source是IntermediateResultPartition,target是ExecutionVertex。source和target都只能是一个。
    • Execution:是执行一个 ExecutionVertex 的一次尝试。当发生故障或者数据需要重算的情况下 ExecutionVertex 可能会有多个 ExecutionAttemptID。一个 Execution 通过 ExecutionAttemptID 来唯一标识。JM和TM之间关于 task 的部署和 task status 的更新都是通过 ExecutionAttemptID 来确定消息接受者。
  • **物理执行图:**JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。
    • Task:Execution被调度后在分配的 TaskManager 中启动对应的 Task。Task 包裹了具有用户执行逻辑的 operator。
    • ResultPartition:代表由一个Task的生成的数据,和ExecutionGraph中的IntermediateResultPartition一一对应。
    • ResultSubpartition:是ResultPartition的一个子分区。每个ResultPartition包含多个ResultSubpartition,其数目要由下游消费 Task 数和 DistributionPattern 来决定。
    • InputGate:代表Task的输入封装,和JobGraph中JobEdge一一对应。每个InputGate消费了一个或多个的ResultPartition。
    • InputChannel:每个InputGate会包含一个以上的InputChannel,和ExecutionGraph中的ExecutionEdge一一对应,也和ResultSubpartition一对一地相连,即一个InputChannel接收一个ResultSubpartition的输出。

为什么有这四张图,目的很简单:就是解耦,每张图各司其职,每张图对应了 Job 不同的阶段,更方便做该阶段的事情(对应软件架构图)