flink学习(四)——flink任务执行原理详解

前文中我们学习了flink的整体架构和任务提交执行的流程。现在我们来学习flink在内部具体如何执行任务。

任务执行图

在flink中有四层执行图,StreamGraph -> JobGraph -> ExecutionGraph -> 物理执行图,如图所示:
flink四层执行图

  • StreamGraph:是根据用户通过 Stream API 编写的代码生成的最初的图,用来表示程序的拓扑结构。每个转换操作会生成一个StreamNode,两个StreamNodes之间由StreamEdge连接在一起,StreamEdge表示的是算子操作之间的数据传递逻辑,整个图直观表现就是各个算子连接在一起形成一个DAG。
  • JobGraph:StreamGraph经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。在一步做了一个优化,就是将多个符合条件的StreamNode节点 chain 在一起作为一个JobVertex节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。JobGraph在Client端生成。
  • ExecutionGraph:JobManager 根据 JobGraph 生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本:一个JobVertex对应一个ExecutionJobVertex,根据一个JobVertex的并行度(一个task可以并行在不同的TaskManager上执行),生成相对应数量的ExecutionVertex。同时在这个图中多了一层IntermediateResult,表示执行的中间结果。IntermediateResult与ExecutionJobVertex之间通过ExecutionEdge形成连接。ExecutionGraph是调度层最核心的数据结构,JobManager通过它来调度任务的执行。
  • 物理执行图:JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,与ExecutionGraph基本保持一致。它只是一个任务执行状态的逻辑展示,并不是一个具体的数据结构。

任务与算子链

根据任务执行图我们可以发现最终在实际机器上执行的就是一个个task。task是flink job中分发执行任务的单元,它由一个算子,或者一组算子链组成。那什么是算子链的?

要理解这个问题,我们可以先看一个简单的例子:现在有个字符串"Hello,10,World",我们要获取其中的单词"Hello"和"World",去除数字,这个怎么做?

这个问题,分为两步:

  1. 将所有子字符串提取出来。
  2. 过滤掉数字,只保留"Hello"和"World"。

这样就需要两个算子:flatMapfilter。因为是两个算子,数字就会在不同的task之间传递,需要序列化/反序列化等操作。

不过这个问题可以进行很简单的优化:在提取子字符串的时候,可以直接过滤掉数字,这两步可以直接优化为一步。

Operator chain算子链做的就是这样一个优化,减少了数据的序列化和数据传输的消耗。不过不是所有的算子之间都是可以进行chain的,只有当下游算子的子任务只依赖了上游算子的一个子任务的时候,才能做算子链优化(与spark中的窄依赖同理)。

具体过程可看下图:
算子链优化
在这个job中, 有6个算子source -> map -> keyBy -> window -> apply -> sink,其中source和map进行了算子链操作链接到了一起,注意keyBy -> window -> apply不是算子链。keyBy和window操作不涉及具体的数据转换,而是数据的聚合分发逻辑,所以它们与apply天然的就形成了一个task。

Task slots(任务槽)

前文我们提到过在TaskManager中存在一个task slot,是任务调度的最小执行单元。这个怎么理解呢?

相信大家都听说过进程和线程。我们可以通过类比进程和线程来理解TaskManagertask slotTaskManager是运行在集群节点上的一个进程,它是负责计算干活的。我们大家都知道,一个进程中可以多个线程。在TaskManager中也是一样的道理,它可以运行多个task,在不同的线程中运行,而运行具体task的地方就叫做task slot,如下图所示:

在这里插入图片描述

一个task slot指代了TaskManager分配给一个任务执行的资源(主要是内存资源,cpu没有做隔离)。如:一个TaskManager上有3个task slot,分配给TaskManager的内存为6G,就表示每个task slot拥有6G / 3 = 2G的内存用来执行任务。不过虽然每个task slot拥有自己独享的内存资源,但是在一个TaskManager上的所有任务共享TCP连接和心跳消息,以节省资源。

Slots共享

在JobGraph转为ExecutionGraph的时候,我们提到了任务的并行度,也即一个task可以同时在多少个task slots上执行。我们看上面那幅图,有2个并行度的任务就会分配2个task slots执行。

那不同的任务可以分配到同一个task slot上执行吗?答案是可以的,但是只能分配同一个job的不同的算子任务。怎么理解这句话,我们从两个方面来理解:

  1. 假设多个job的算子的子任务可以分配在同一个slot上面执行,那多个job之间就会互相影响,有一个job的任务失败了,可能就会导致另外一个job的任务也失败。所以任务不能跨job分配到同一个slot上。
  2. 假设同一个job的同一个算子的子任务在同一个slot上执行,那这个就没有并行了,本来我们有多个子任务就是为了能够并行。所以同一个job的不同算子的子任务也不能分配到同一个slot上。

那么剩下的选择就只有同一个job的不同算子的子任务可以分配到同一个slot上,而这个是可以的,而且就应该这样做。为什么呢?

1. 充分利用计算资源

因为我们的数据是流式的,一条一条进来的,可能前一个算子已经处理完了,交给下一个算子进行处理,这时候如果上下游算子都是分开在不同的slot上,那么首先导致数据传输不说,假设有个算子执行计算很复杂,耗时很长,那就会导致闲的slot闲死,忙的slot忙死。而将同一个job的不同算子的子任务放在同一个slot上执行,就可以避免这个问题:已经执行完的算子可以让出资源给计算复杂的算子进行计算,如下图所示:在这里插入图片描述

source -> map -> keyBy -> window -> apply -> sink所有的算子的某一个子任务可以分配在同一个slot里面执行,但是同一个算子的不同子任务是在不同的slot中的。source和map的计算简单,一会就结束了,而keyBy/window/apply的操作可能很复杂,要算好久,但是因为它们在同一个slot中,不会占用其他资源,source和map算完了,资源直接让给keyBy/window/apply算子进行计算即可。

2. 方便flink集群计算并发资源

Slots共享可以极大地提高资源利用率,同时也方便了flink集群计算并发资源。如果没有slots共享,flink集群要遍历所有的算子,获取它们的并行度,相加起来才是最终整个job需要的slots数量。首先这个计算复杂,而且很难去说明这个job的并行度。假设我们有个job有100个算子,每个算子自己的并行度为1,如果这样算,就需要有100个slots,那么到底job的并行度是100呢还是1呢?有点说不清楚。

而有了slots共享之后,可以直接取所有算子中最大的并行度,作为整个job的并行度,不仅方便了计算,而且我们直接取这个最大并行度作为job的并行度即可。方便了计算,也方便了理解。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值