暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

91. Flink任务执行与资源划分

大数据技能圈 2023-05-31
44
逻辑视图到物理执行图
Flink任务执行可以说是逻辑视图转化为物理执行图的过程,该过程可以分成4层:StreamGraph→JobGraph→ExecutionGraph→物理执行图

StreamGraph:根据编写的代码生成的最初的图,用来表示一个Flink流处理作业的拓扑结构。在StreamGraph中,节点StreamNode就是算子。

JobGraph:JobGraph是被提交给JobManager的数据结构。StreamGraph经过优化后生成了JobGraph,主要的优化为,将多个符合条件的节点链接在一起作为一个JobVertex节点,这样可以减少数据交换所需要的传输开销。这个链接的过程叫算子链(Operator Chain)。JobVertex经过算子链后,会包含一到多个算子,它的输出是IntermediateDataSet,这是经过算子处理产生的数据集。

ExecutionGraph:JobManager将JobGraph转化为ExecutionGraph。ExecutionGraph是JobGraph的并行化版本:假如某个JobVertex的并行度是2,那么它将被划分为2个ExecutionVertex,ExecutionVertex表示一个算子子任务,它监控着单个子任务的执行情况。每个ExecutionVertex会输出一个IntermediateResultPartition,这是单个子任务的输出,再经过ExecutionEdge输出到下游节点。ExecutionJobVertex是这些并行子任务的合集,它监控着整个算子的执行情况。ExecutionGraph是调度层非常核心的数据结构。

物理执行图:JobManager根据ExecutionGraph对作业进行调度后,在各个TaskManager上部署具体的任务,物理执行图并不是一个具体的数据结构。可以看到,Flink在数据流图上可谓煞费苦心。Flink采用主从架构,Master起着管理协调作用,TaskManager负责物理执行,在执行过程中会发生一些如数据交换、生命周期管理等事情。用户调用Flink API,构造逻辑视图,Flink会对逻辑视图优化,并转化为并行化的物理执行图,最后被执行的是物理执行图。

任务、算子子任务与算子链
在构造物理执行图的过程中,Flink会将一些算子子任务链接在一起,组成算子链。链接后以任务(Task)的形式被TaskManager调度执行。使用算子链是一个非常有效的优化,它可以有效减少算子子任务之间的传输开销。链接之后形成的任务是TaskManager中的一个线程。图3-8展示了任务、子任务和算子链之间的关系。
例如,数据从Source前向传播到FlatMap,这中间没有发生跨分区的数据交换,因此,我们完全可以将Source、FlatMap这两个子任务组合在一起,形成一个任务。数据经过keyBy()发生了数据交换,数据会跨越分区,因此无法将keyBy()以及其后面的窗口聚合、链接到一起。由于WindowAggregation的并行度为2、Sink的并行度为1,数据再次发生了交换,我们不能把WindowAggregation和Sink两部分链接到一起。3.1节中提到,Sink的并行度被人为设置为1,如果我们把Sink的并行度也设置为2,那么是可以让这两个算子链接到一起的。
默认情况下,Flink会尽量将更多的子任务链接在一起,这样能减少一些不必要的数据传输开销。但一个子任务有超过一个输入或发生数据交换时,链接就无法建立。Flink源码中org.apache.flink.streaming.api.graph.StreamingJobGraphGenerator中的isChainable()方法。StreamingJobGraphGenerator类的作用是将StreamGraph转换为JobGraph。

尽管将算子链接到一起会减少一些传输开销,但是也有一些情况并不需要太多链接。比如,有时候我们需要将一个非常长的算子链拆开,这样我们就可以将原来集中在一个线程中的计算拆分到多个线程中来并行计算。Flink允许开发者手动配置是否启用算子链,或者对哪些算子使用算子链。我们也将在后续讨论算子链的具体使用方法。

Slot与计算资源

1.Slot

根据前文的介绍,我们已经了解到TaskManager负责具体的任务执行。在程序执行之前,经过优化,部分子任务被链接在一起,组成一个任务。

TaskManager是一个JVM进程,在TaskManager中可以并行执行一到多个任务。每个任务是一个线程,需要TaskManager为其分配相应的资源,TaskManager使用Slot给任务分配资源。

在解释Flink的Slot的概念前,我们先回顾一下进程与线程的概念。在操作系统层面,进程(Process)是进行资源分配和调度的一个独立单位,线程(Thread)是CPU调度的基本单位。比如,我们常用的Office Word软件,在启动后就占用操作系统的一个进程。Windows上可以使用任务管理器来查看当前活跃的进程,Linux上可以使用top命令来查看。线程是进程的一个子集,一个线程一般专注于处理一些特定任务,不独立拥有系统资源,只拥有一些执行中必要的资源,如程序计数器。一个进程至少有一个线程,也可以有多个线程。多线程场景下,每个线程都处理一个任务,多个线程以高并发的方式同时处理多个任务,可以提高处理能力。

回到Flink的Slot分配机制上,一个TaskManager是一个进程,TaskManager可以管理一至多个任务,每个任务是一个线程,占用一个Slot。每个Slot的资源是整个TaskManager资源的子集,比如图3-9所示的TaskManager下有3个Slot,每个Slot占用TaskManager 1/3的内存,第一个Slot中的任务不会与第二个Slot中的任务互相争抢内存资源。

注意,在分配资源时,Flink并没有将CPU资源明确分配给各个Slot。

假设我们给WordCount程序分配两个TaskManager,每个TaskManager又分配3个Slot,所以共有6个Slot。结合图3-8所示的对这个作业并行度的设置,整个作业被划分为5个任务,使用5个线程,这5个线程可以按照上图所示的方式分配到6个Slot中。

Flink允许用户设置TaskManager中Slot的数目,这样用户就可以确定以怎样的粒度将任务做相互隔离。如果每个TaskManager只包含一个Slot,那么该Slot内的任务将独享JVM。如果TaskManager包含多个Slot,那么多个Slot内的任务可以共享JVM资源,比如共享TCP连接、心跳信息、部分数据结构等。官方建议将Slot数目设置为TaskManager下可用的CPU核心数,那么平均下来,每个Slot都能获得1个CPU核心。

2.槽位共享

上图展示了任务的一种资源分配方式,默认情况下,Flink还提供了一种槽位共享(Slot Sharing)的优化机制,进一步减少数据传输开销,充分利用计算资源。将上图所示的任务做槽位共享优化后,结果下图所示。

开启槽位共享后,Flink允许多个任务共享一个Slot。如图上所示,最左侧的数据流,一个作业从Source到Sink的所有子任务都可以放置在一个Slot中,这样数据交换成本更低。而且,对于一个数据流图来说,Source、FlatMap等算子的计算量相对不大,WindowAggregation算子的计算量比较大,计算量较大的算子子任务与计算量较小的算子子任务可以互补,空出更多的槽位,分配给更多任务,这样可以更好地利用资源。如果不开启槽位共享,如上图所示,计算量小的Source、FlatMap算子子任务独占槽位,造成一定的资源浪费。

上图所示的方式共占用5个Slot,支持槽位共享后,如上图所示,只占用2个Slot。为了充分利用空Slot,剩余的4个空Slot可以分配给别的作业,也可以通过修改并行度来分配给该作业。例如,该作业的输入数据量非常大,我们可以把并行度设为6,更多的算子子任务会将这些Slot填充,如下图所示。

综上,Flink的一个Slot中可以执行一个算子子任务、也可以是被链接的多个子任务组成的任务,或者是共享Slot的多个任务,具体这个Slot上执行哪些计算由算子链和槽位共享两个优化措施决定。我会在后续继续讨论算子链和槽位共享这两个优化选项。

并行度和Slot数目的概念可能容易让人混淆,这里再次阐明一下。使用Flink提供的API算子可以构建一个逻辑视图,需要将任务并行才能被物理执行。一个算子将被切分为多个子任务,每个子任务处理整个作业输入数据的一部分。如果输入数据过大,增大并行度可以让算子切分为更多的子任务,加快数据处理速度。可见,并行度是Flink对任务并行切分的一种描述。Slot数目是在资源设置时,对单个TaskManager的资源切分粒度。关于并行度、Slot数目等配置,将在后续详细说明。

更多大数据相关内容请关注大数据技能圈公众号:

文章转载自大数据技能圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论