| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> Flink并行度和算子链以及数据图执行图 -> 正文阅读 |
|
[大数据]Flink并行度和算子链以及数据图执行图 |
? ? ?我们现在已经了解
Flink
运行时的核心组件和整体架构,也明白了不同场景下作业提交的
具体流程。但有些细节还需要进一步思考:一个具体的作业,是怎样从我们编写的代码,转换
成
TaskManager
可以执行的任务的呢?
JobManager
收到提交的作业,又是怎样确定总共有多
少任务、需要多少资源呢?接下来我们就从一些重要概念入手,对这些问题做详细的展开讲解。
数据流图(Dataflow Graph)
? ? ? ? Flink
是流式计算框架。它的程序结构,其实就是定义了一连串的处理操作,每一个数据
输入之后都会依次调用每一步计算。在
Flink
代码中,我们定义的每一个处理转换操作都叫作
“
算子
”
(
Operator
),所以我们的程序可以看作是一串算子构成的管道,数据则像水流一样有序
地流过。比如在之前的
WordCount
代码中,基于执行环境调用的
socketTextStream()
方法,就
是一个读取文本流的算子;而后面的
flatMap()
方法,则是将字符串数据进行分词、转换成二
元组的算子。
所有的
Flink
程序都可以归纳为由三部分构成:
Source
、
Transformation
和
Sink
。
?
Source
表示
“
源算子
”
,负责读取数据源。
?
Transformation
表示
“
转换算子
”
,利用各种算子进行处理加工。
?
Sink
表示
“
下沉算子
”
,负责数据的输出。
? ? ? ? 在运行时,
Flink
程序会被映射成所有算子按照逻辑顺序连接在一起的一张图,这被称为
“
逻辑数据流
”
(
logical dataflow
),或者叫
“
数据流图
”
(
dataflow graph
)。我们提交作业之后,
打开
Flink
自带的
Web UI
,点击作业就能看到对应的
dataflow
,如图
4-7
所示。在数据流图中,
可以清楚地看到
Source
、
Transformation
、
Sink
三部分。
? ? ? ?数据流图类似于任意的有向无环图(
DAG
),这一点与
Spark
等其他框架是一致的。图中
的每一条数据流(
dataflow
)以一个或多个
source
算子开始,以一个或多个
sink
算子结束。
? ? ? ?在大部分情况下,
dataflow
中的算子,和程序中的转换运算是一一对应的关系。那是不是
说,我们代码中基于
DataStream API
的每一个方法调用,都是一个算子呢?
? ? ? ?并非如此。除了
Source
读取数据和
Sink
输出数据,一个中间的转换算子(
Transformation
Operator
)必须是一个转换处理的操作;而在代码中有一些方法调用,数据是没有完成转换的。
?
可能只是对属性做了一个设置,也可能定义的是数据的传递方式而非转换,又或者是需要几个方法合在一起才能表达一个完整的转换操作。例如,在之前的代码中,我们用到了定义分组的
方法
keyBy
,它就只是一个数据分区操作,而并不是一个算子。事实上,代码中我们可以看到
调用其他转换操作之后返回的数据类型是
SingleOutputStreamOperator
,说明这是一个算子操
作;而
keyBy
之后返回的数据类型是
KeyedStream
。感兴趣的读者也可以自行提交任务在
Web
UI
中查看。
并行度(Parallelism)
我们已经清楚了算子和数据流图的概念,那最终执行的任务又是什么呢?容易想到,一个
算子操作就应该是一个任务。那是不是程序中的算子数量,就是最终执行的任务数呢?
1. 什么是并行计算??
? ? ? ?要解答这个问题,我们需要先梳理一下其他框架分配任务、数据处理的过程。对于
Spark
而言,是把根据程序生成的
DAG
划分阶段(
stage
)、进而分配任务的。而对于
Flink
这样的流
式引擎,其实没有划分
stage
的必要。因为数据是连续不断到来的,我们完全可以按照数据流
图建立一个“流水线”,前一个操作处理完成,就发往处理下一步操作的节点。如果说
Spark
基于
MapReduce
架构的思想是“数据不动代码动”,那么
Flink
就类似“代码不动数据流动”,
原因就在于流式数据本身是连续到来的、我们不会同时传输所有数据,这其实是更符合数据流
本身特点的处理方式。
? ? ? ?在大数据场景下,我们都是依靠分布式架构做并行计算,从而提高数据吞吐量的。既然处
理完一个操作就可以把数据发往别处,那我们就可以将不同的算子操作任务,分配到不同的节
点上执行了。这样就对任务做了分摊,实现了并行处理。
? ? ? ?但是仔细分析会发现,这种“并行”其实并不彻底。因为算子之间是有执行顺序的,对一
条数据来说必须依次执行;而一个算子在同一时刻只能处理一个数据。比如之前
WordCount
,
一条数据到来之后,我们必须先用
source
算子读进来、再做
flatMap
转换;一条数据被
source
读入的同时,之前的数据可能正在被
flatMap
处理,这样不同的算子任务是并行的。但如果多
条数据同时到来,一个算子是没有办法同时处理的,我们还是需要等待一条数据处理完、再处
理下一条数据——这并没有真正提高吞吐量。
? ? ? ?所以相对于上述的“任务并行”,我们真正关心的,是“数据并行”。也就是说,多条数据
同时到来,我们应该可以同时读入,同时在不同节点执行
flatMap
操作。
2. 并行子任务和并行度
? ? ? ?怎样实现数据并行呢?其实也很简单,我们把一个算子操作,“复制”多份到多个节点,
数据来了之后就可以到其中任意一个执行。这样一来,一个算子任务就被拆分成了多个并行的
“子任务”(
subtasks
),再将它们分发到不同节点,就真正实现了并行计算。
? ? ? 在
Flink
执行过程中,每一个算子(
operator
)可以包含一个或多个子任务(
operator subtask
), 这些子任务在不同的线程、不同的物理机或不同的容器中完全独立地执行。
? ? ? ? ? 一个特定算子的子任务(
subtask
)的个数被称之为其并行度(
parallelism
)。这样,包含并
行子任务的数据流,就是并行数据流,它需要多个分区(
stream partition
)来分配并行任务。
? ? ? ? 一般情况下,一个流程序的并行度,可以认为就是其所有算子中最大的并行度。一个程序中, 不同的算子可能具有不同的并行度。 如图 4-8
所示,当前数据流中有
source
、
map
、
window
、
sink
四个算子,除最后
sink
,其 他算子的并行度都为 2
。整个程序包含了
7
个子任务,至少需要
2
个分区来并行执行。我们可 以说,这段流处理程序的并行度就是 2
。
3. 并行度的设置
? ?在
Flink
中,可以用不同的方法来设置并行度,它们的有效范围和优先级别也是不同的。
(
1
)代码中设置
我们在代码中,可以很简单地在算子后跟着调用
setParallelism()
方法,来设置当前算子的
并行度:
这种方式设置的并行度,只针对当前算子有效。
另外,我们也可以直接调用执行环境的
setParallelism()
方法,全局设定并行度:
这样代码中所有算子,默认的并行度就都为
2
了。
我们一般不会在程序中设置全局并行度,
因为如果在程序中对全局并行度进行硬编码,会导致无法动态扩容。
这里要注意的是,由于
keyBy
不是算子,所以无法对
keyBy
设置并行度。
(2)提交应用时设置
在使用
flink run
命令提交应用时,可以增加
-p
参数来指定当前应用程序执行的并行度,
它的作用类似于执行环境的全局设置:
如果我们直接在
Web UI
上提交作业,也可以在对应输入框中直接添加并行度。
(3)配置文件中设置
我们还可以直接在集群的配置文件
flink-conf.yaml
中直接更改默认并行度:
????????这个设置对于整个集群上提交的所有作业有效,初始值为 1
。无论在代码中设置、还是提
交时的
-p
参数,都不是必须的;所以在没有指定并行度的时候,就会采用配置文件中的集群
默认并行度。在开发环境中,没有配置文件,默认并行度就是当前机器的
CPU
核心数。这也
就解释了为什么我们在第二章运行
WordCount
流处理程序时,会看到结果前有
1~4
的分区编
号——运行程序的电脑是
4
核
CPU
,那么开发环境默认的并行度就是
4
。
我们可以总结一下所有的并行度设置方法,它们的优先级如下:
(
1
)对于一个算子,首先看在代码中是否单独指定了它的并行度,这个特定的设置优先级
最高,会覆盖后面所有的设置。
(2)如果没有单独设置,那么采用当前代码中执行环境全局设置的并行度。
(3)如果代码中完全没有设置,那么采用提交时
-p
参数指定的并行度。
(4)如果提交时也未指定
-p
参数,那么采用集群配置文件中的默认并行度。
????????这里需要说明的是,算子的并行度有时会受到自身具体实现的影响。比如之前我们用到的
读取
socket
文本流的算子
socketTextStream
,它本身就是非并行的
Source
算子,所以无论怎么
设置,它在运行时的并行度都是
1
,对应在数据流图上就只有一个并行子任务。这一点大家可
以自行在
Web UI
上查看验证。
那么实践中怎样设置并行度比较好呢?那就是在代码中只针对算子设置并行度,不设置全
局并行度,这样方便我们提交作业时进行动态扩容。
?算子链(Operator Chain)
????????关于“一个作业有多少任务”这个问题,现在已经基本解决了。但如果我们仔细观察 Web
UI
上给出的图,如图
4-9
所示,上面的节点似乎跟代码中的算子又不是一一对应的。
????????很明显,这里的一个节点,会把转换处理的很多个任务都连接在一起,合并成了一个“大
任务”。这又是怎么回事呢?
1. 算子间的数据传输
回到上一小节的例子,我们先来考察一下算子任务之间数据传输的方式。
????????如图 4-10
所示,一个数据流在算子之间传输数据的形式可以是一对一(
one-to-one
)的直
通
(forwarding)
模式,也可以是打乱的重分区(
redistributing
)模式,具体是哪一种形式,取决
于算子的种类。
(1)一对一(One-to-one,forwarding)
????????这种模式下,数据流维护着分区以及元素的顺序。比如图中的 source
和
map
算子,
source
算子读取数据之后,可以直接发送给
map
算子做处理,它们之间不需要重新分区,也不需要
调整数据的顺序。这就意味着
map
算子的子任务,看到的元素个数和顺序跟
source
算子的子
任务产生的完全一样,保证着
“
一对一
”
的关系。
map
、
filter
、
flatMap
等算子都是这种
one-to-one
的对应关系。
????????
这种关系类似于 Spark 中的窄依赖。
(2)重分区(Redistributing)
????????在这种模式下,数据流的分区会发生改变。比图中的 map
和后面的
keyBy/window
算子之
间(这里的
keyBy
是数据传输算子,后面的
window
、
apply
方法共同构成了
window
算子)
,
以及
keyBy/window
算子和
Sink
算子之间,都是这样的关系。
????????每一个算子的子任务,会根据数据传输的策略,把数据发送到不同的下游目标任务。例如,
keyBy()
是分组操作,本质上基于键(
key
)的哈希值(
hashCode
)进行了重分区;而当并行度
改变时,比如从并行度为
2
的
window
算子,要传递到并行度为
1
的
Sink
算子,这时的数据
传输方式是再平衡(
rebalance
),会把数据均匀地向下游子任务分发出去。这些传输方式都会
引起重分区(
redistribute
)的过程,这一过程类似于
Spark
中的
shuffle
。
总体说来,这种算子间的关系类似于 Spark 中的宽依赖。
2. 合并算子链
????????在 Flink
中,并行度相同的一对一(
one to one
)算子操作,可以直接链接在一起形成一个
“大”的任务(
task
),这样原来的算子就成为了真正任务里的一部分,如图
4-11
所示。每个
task
会被一个线程执行。这样的技术被称为“算子链”(
Operator Chain
)。
????????比如在图 4-11
中的例子中,
Source
和
map
之间满足了算子链的要求,所以可以直接合并
在一起,形成了一个任务;因为并行度为
2
,所以合并后的任务也有两个并行子任务。这样,
这个数据流图所表示的作业最终会有
5
个任务,由
5
个线程并行执行。
????????Flink 为什么要有算子链这样一个设计呢?这是因为将算子链接成 task
是非常有效的优
化:可以减少线程之间的切换和基于缓存区的数据交换,在减少时延的同时提升吞吐量。
????????Flink 默认会按照算子链的原则进行链接合并,如果我们想要禁止合并或者自行定义,也
可以在代码中对算子做一些特定的设置:
作业图(JobGraph)与执行图(ExecutionGraph)
????????至此,我们已经彻底了解了由代码生成任务的过程,现在来做个梳理总结。
????????由 Flink
程序直接映射成的数据流图(
dataflow graph
),也被称为逻辑流图(
logical
StreamGraph
),因为它们表示的是计算逻辑的高级视图。到具体执行环节时,我们还要考虑并
行子任务的分配、数据在任务间的传输,以及合并算子链的优化。为了说明最终应该怎样执行
一个流处理程序,
Flink
需要将逻辑流图进行解析,转换为物理数据流图。
????????在这个转换过程中,有几个不同的阶段,会生成不同层级的图,其中最重要的就是作业图
(
JobGraph
)和执行图(
ExecutionGraph
)。
Flink
中任务调度执行的图,按照生成顺序可以分成
四层:
逻辑流图(StreamGraph)→ 作业图(JobGraph)→ 执行图(ExecutionGraph)→ 物理
图(Physical Graph)。
????????我们可以回忆一下之前处理 socket
文本流的
StreamWordCount
程序:
????????如果提交时设置并行度为 2
:
????????那么根据之前的分析,除了 socketTextStream()
是非并行的
Source
算子,它的并行度始终
为
1
,其他算子的并行度都为
2
。
????????接下来我们分析一下程序对应四层调度图的演变过程,如图 4-12
所示。
1. 逻辑流图(StreamGraph)
????????这是根据用户通过 DataStream API
编写的代码生成的最初的
DAG
图,用来表示程序的拓
扑结构。这一步一般在客户端完成。
????????我们可以看到,逻辑流图中的节点,完全对应着代码中的四步算子操作:
源算子 Source(socketTextStream())→扁平映射算子 Flat Map(flatMap()) →分组聚合算子
Keyed Aggregation(keyBy/sum()) →输出算子 Sink(print())。
2. 作业图(JobGraph)
????????StreamGraph 经过优化后生成的就是作业图(
JobGraph
),这是提交给
JobManager
的数据
结构,确定了当前作业中所有任务的划分。
主要的优化为: 将多个符合条件的节点链接在一起
合并成一个任务节点,形成算子链,这样可以减少数据交换的消耗。JobGraph 一般也是在客
户端生成的,在作业提交时传递给 JobMaster。
????????在图 4-12
中,分组聚合算子(
Keyed Aggregation
)和输出算子
Sink(print)
并行度都为
2
,
而且是一对一的关系,满足算子链的要求,所以会合并在一起,成为一个任务节点。
3. 执行图(ExecutionGraph)
????????JobMaster 收到
JobGraph
后,会根据它来生成执行图(
ExecutionGraph
)。
ExecutionGraph 是 JobGraph
的并行化版本,是调度层最核心的数据结构。
????????从图 4-12
中可以看到,与
JobGraph
最大的区别就是按照并行度对并行子任务进行了拆分,
并明确了任务间数据传输的方式。
4. 物理图(Physical Graph)
????????JobMaster 生成执行图后, 会将它分发给
TaskManager
;各个
TaskManager
会根据执行图
部署任务,最终的物理执行过程也会形成一张“图”,一般就叫作物理图(Physical Graph)。
????????这只是具体执行层面的图,并不是一个具体的数据结构。
????????对应在上图 4-12
中,物理图主要就是在执行图的基础上,进一步确定数据存放的位置和
收发的具体方式。有了物理图,
TaskManager
就可以对传递来的数据进行处理计算了。
????????所以我们可以看到,程序里定义了四个算子操作:源(Source
)
->
转换(
flatMap
)
->
分组
聚合(
keyBy/sum
)
->
输出(
print
);合并算子链进行优化之后,就只有三个任务节点了;再考
虑并行度后,一共有
5
个并行子任务,最终需要
5
个线程来执行。
任务(task)和任务槽(slots)1. 任务槽(Task Slots)
????????之前已经提到过,Flink
中每一个
worker(
也就是
TaskManager)
都是一个
JVM
进程,它可
以启动多个独立的线程,来并行执行多个子任务(
subtask
)。 所以如果想要执行 5
个任务,并不一定非要
5
个
TaskManager
,我们可以让
TaskManager 多线程执行任务。如果可以同时运行 5
个线程,那么只要一个
TaskManager
就可以满足我们之 前程序的运行需求了。
????????很显然,TaskManager
的计算资源是有限的,并不是所有任务都可以放在一个
TaskManager
上并行执行。并行的任务越多,每个线程的资源就会越少。那一个
TaskManager
到底能并行处
理多少个任务呢?为了控制并发量,我们需要在
TaskManager
上对每个任务运行所占用的资源
做出明确的划分,这就是所谓的任务槽(
task slots
)。
????????每个任务槽(task slot
)其实表示了
TaskManager
拥有计算资源的一个固定大小的子集。
这些资源就是用来独立执行一个子任务的。
![]()
????????假如一个 TaskManager
有三个
slot
,那么它会将管理的内存平均分成三份,每个
slot
独自
占据一份。这样一来,我们在
slot
上执行一个子任务时,相当于划定了一块内存
“
专款专用
”
,
就不需要跟来自其他作业的任务去竞争内存资源了。所以现在我们只要
2
个
TaskManager
,就
可以并行处理分配好的
5
个任务了,如图
4-14
所示。
2. 任务槽数量的设置
????????我们可以通过集群的配置文件来设定 TaskManager
的
slot
数量:
????????通过调整 slot
的数量,我们就可以控制子任务之间的隔离级别。
?
????????具体来说,如果一个 TaskManager
只有一个
slot
,那将意味着每个任务都会运行在独立的
JVM
中(当然,该
JVM
可能是通过一个特定的容器启动的);而一个
TaskManager
设置多个
slot
则意味着多个子任务可以共享同一个
JVM
。它们的区别在于:前者任务之间完全独立运行,
隔离级别更高、彼此间的影响可以降到最小;而后者在同一个
JVM
进程中运行的任务,将共
享
TCP
连接和心跳消息,也可能共享数据集和数据结构,这就减少了每个任务的运行开销,
在降低隔离级别的同时提升了性能。
????????
需要注意的是,slot 目前仅仅用来隔离内存,不会涉及 CPU 的隔离。在具体应用时,可
以将 slot 数量配置为机器的 CPU 核心数,尽量避免不同任务之间对 CPU 的竞争。这也是开发
环境默认并行度设为机器 CPU 数量的原因。
3. 任务对任务槽的共享
????????这样看来,一共有多少任务,我们就需要有多少 slot
来并行处理它们。不过实际提交作业
进行测试就会发现,我们之前的
WordCount
程序设置并行度为
2
提交,一共有
5
个并行子任
务,可集群即使只有
2
个
task slot
也是可以成功提交并运行的。这又是为什么呢?
????????我们可以基于之前的例子继续扩展。如果我们保持 sink
任务并行度为
1
不变,而作业提
交时设置全局并行度为
6
,那么前两个任务节点就会各自有
6
个并行子任务,整个流处理程序
则有
13
个子任务。那对于
2
个
TaskManager
、每个有
3
个
slot
的集群配置来说,还能否正常
运行呢
![]()
????????完全没有问题。这是因为默认情况下,Flink
是允许子任务共享
slot
的。如图
4-15
所示,
只要属于同一个作业,那么对于不同任务节点的并行子任务,就可以放到同一个
slot
上执行。
所以对于第一个任务节点
source
→
map
,它的
6
个并行子任务必须分到不同的
slot
上(如果在
同一
slot
就没法数据并行了),而第二个任务节点
keyBy/window/apply
的并行子任务却可以和
第一个任务节点共享
slot
。
????????于是最终结果就变成了:每个任务节点的并行子任务一字排开,占据不同的 slot
;而不同
的任务节点的子任务可以共享
slot
。一个
slot
中,可以将程序处理的所有任务都放在这里执行,
我们把它叫作保存了整个作业的运行管道(
pipeline
)。
????????这个特性看起来有点奇怪:我们不是希望并行处理、任务之间相互隔离吗,为什么这里又
允许共享
slot
呢?
????????我们知道,一个 slot
对应了一组独立的计算资源。在之前不做共享的时候,每个任务都平
等地占据了一个
slot
,但其实不同的任务对资源的占用是不同的。例如这里的前两个任务,
source/map
尽管是两个算子合并算子链得到的,但它只是基本的数据读取和简单转换,计算耗
时极短,一般也不需要太大的内存空间;而
window
算子所做的窗口操作,往往会涉及大量的
数据、状态存储和计算,我们一般把这类任务叫作“资源密集型”(
intensive
)任务。当它们
被平等地分配到独立的
slot
上时,实际运行我们就会发现,大量数据到来时
source/map
和
sink
任务很快就可以完成,但
window
任务却耗时很久;于是下游的
sink
任务占据的
slot
就会等待
闲置,而上游的
source/map
任务受限于下游的处理能力,也会在快速处理完一部分数据后阻
塞对应的资源开始等待(相当于处理背压)。这样资源的利用就出现了极大的不平衡,“忙的忙
死,闲的闲死”。
????????解决这一问题的思路就是允许 slot
共享。当我们将资源密集型和非密集型的任务同时放到
一个
slot
中,它们就可以自行分配对资源占用的比例,从而保证最重的活平均分配给所有的
TaskManager
。
????????slot 共享另一个好处就是允许我们保存完整的作业管道。这样一来,即使某个
TaskManager
出现故障宕机,其他节点也可以完全不受影响,作业的任务可以继续执行。
????????另外,同一个任务节点的并行子任务是不能共享 slot
的,所以允许
slot
共享之后,运行作
业所需的
slot
数量正好就是作业中所有算子并行度的最大值。这样一来,我们考虑当前集群需
要配置多少
slot
资源时,就不需要再去详细计算一个作业总共包含多少个并行子任务了,只看
最大的并行度就够了。
????????当然,Flink
默认是允许
slot
共享的,如果希望某个算子对应的任务完全独占一个
slot
,
或者只有某一部分算子共享
slot
,我们也可以通过设置
“slot
共享组
”
(
SlotSharingGroup
)手动
指定:
???????
?这样,只有属于同一个 slot 共享组的子任务,才会开启 slot 共享;不同组之间的任务是完
全隔离的,必须分配到不同的 slot 上。在这种场景下,总共需要的 slot 数量,就是各个 slot
共享组最大并行度的总和。
? ? ? ? 默认情况下,所有算子都在default共享组下,所以slot可以共享,但是某一个算子单独设置共享组,在他之后的算子操作默认都和他一样在同一个共享组;除非后面的算子也单独设置共享组了。
4. 任务槽和并行度的关系
????????直观上看,slot
就是
TaskManager
为了并行执行任务而设置的,那它和之前讲过的并行度
(
Parallelism
)是不是一回事呢?
????????Slot 和并行度确实都跟程序的并行执行有关,但两者是完全不同的概念。简单来说,
task
slot
是 静 态 的 概 念 , 是 指
TaskManager
具 有 的 并 发 执 行 能 力 , 可 以 通 过 参 数
taskmanager.numberOfTaskSlots
进行配置;而并行度(
parallelism
)是动态概念,也就是
TaskManager
运行程序时实际使用的并发能力,可以通过参数
parallelism.default
进行配置。换
句话说,并行度如果小于等于集群中可用
slot
的总数,程序是可以正常执行的,因为
slot
不一
定要全部占用,有十分力气可以只用八分;而如果并行度大于可用
slot
总数,导致超出了并行
能力上限,那么心有余力不足,程序就只好等待资源管理器分配更多的资源了。
????????下面我们再举一个具体的例子。假设一共有 3
个
TaskManager
,每一个
TaskManager
中的
slot
数量设置为
3
个,那么一共有
9
个
task slot
,如图
4-16
所示,表示集群最多能并行执行
9
个任务。
????????而我们定义 WordCount
程序的处理操作是四个转换算子:
????????source→
flatMap
→
reduce
→
sink
????????当所有算子并行度相同时,容易看出 source
和
flatMap
可以合并算子链,于是最终有三个
任务节点。
????????如果我们没有任何并行度设置,而配置文件中默认 parallelism.default=1
,那么程序运行的
默认并行度为
1
,总共有
3
个任务。由于不同算子的任务可以共享任务槽,所以最终占用的
slot
只有
1
个。
9
个
slot
只用了
1
个,有
8
个空闲,如图
4-17
中的
Example 1
所示。
![]()
????????如果我们更改默认参数,或者提交作业时设置并行度为 2
,那么总共有
6
个任务,共享任
务槽之后会占用
2
个
slot
,如图
4-18
中
Example 2
所示。同样,就有
7
个
slot
空闲,计算资源
没有充分利用。所以可以看到,设置合适的并行度才能提高效率。
????????那对于这个例子,怎样设置并行度效率最高呢?当然是需要把所有的 slot
都利用起来。考
虑到
slot
共享,我们可以直接把并行度设置为
9
,这样所有
27
个任务就会完全占用
9
个
slot
。
这是当前集群资源下能执行的最大并行度,计算资源得到了充分的利用,如图
4-19
中
Example
3
所示。
????????另外再考虑对于某个算子单独设置并行度的场景。例如,如果我们考虑到输出可能是写入
文件,那会希望不要并行写入多个文件,就需要设置
sink
算子的并行度为
1
。这时其他的算子
68
并行度依然为
9
,所以总共会有
19
个子任务。根据
slot
共享的原则,它们最终还是会占用全
部的
9
个
slot
,而
sink
任务只在其中一个
slot
上执行,如图
4-20
中
Example 4
所示。通过这个
例子也可以明确地看到,整个流处理程序的并行度,就应该是所有算子并行度中最大的那个,
这代表了运行程序需要的
slot
数量。
? ? |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年4日历 | -2025/4/22 8:27:07- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |