Flink(三)

数据流图     

  Flink 是流式计算框架。它的程序结构,其实就是定义了一连串的处理操作,每一个数据输入之后都会依次调用每一步计算。在 Flink 代码中,我们定义的每一个处理转换操作都叫作“算子”(Operator),所以我们的程序可以看作是一串算子构成的管道,数据则像水流一样有序地流过。比如在之前的 WordCount 代码中,基于执行环境调用的 socketTextStream()方法,就是一个读取文本流的算子;而后面的 flatMap()方法,则是将字符串数据进行分词、转换成二元组的算子。

        在运行时,Flink 程序会被映射成所有算子按照逻辑顺序连接在一起的一张图,这被称为“逻辑数据流”(logical dataflow),或者叫“数据流图”(dataflow graph)。我们提交作业之后,打开 Flink 自带的 Web UI,点击作业就能看到对应的 dataflow,如图 4-7 所示。在数据流图中, 可以清楚地看到 Source、Transformation、Sink 三部分。

数据流图类似于任意的有向无环图(DAG),这一点与 Spark 等其他框架是一致的。图中的每一条数据流(dataflow)以一个或多个 source 算子开始,以一个或多个 sink 算子结束。

在大部分情况下,dataflow 中的算子,和程序中的转换运算是一一对应的关系。那是不是说,我们代码中基于DataStream API 的每一个方法调用,都是一个算子呢?

并非如此。除了 Source 读取数据和 Sink 输出数据,一个中间的转换算子必须是一个转换处理的操作;而在代码中有一些方法调用,数据是没有完成转换的。可能只是对属性做了一个设置,也可能定义的是数据的传递方式而非转换,又或者是需要几个方法合在一起才能表达一个完整的转换操作。例如,在之前的代码中,我们用到了定义分组的方法 keyBy,它就只是一个数据分区操作,而并不是一个算子。事实上,代码中我们可以看到调用其他转换操作之后返回的数据类型是 SingleOutputStreamOperator,说明这是一个算子操作;而 keyBy 之后返回的数据类型是 KeyedStream。感兴趣的读者也可以自行提交任务在 Web UI 中查看。

并行度

我们已经清楚了算子和数据流图的概念,那最终执行的任务又是什么呢?容易想到,一个算子操作就应该是一个任务。那是不是程序中的算子数量,就是最终执行的任务数呢?

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()方法,来设置当前算子的并行度:

stream.map(word -> Tuple2.of(word, 1L)).setParallelism(2);

(这种方式设置的并行度,只针对当前算子有效)

另外,我们也可以直接调用执行环境的 setParallelism()方法,全局设定并行度:

env.setParallelism(2);

       这样代码中所有算子,默认的并行度就都为 2 了。我们一般不会在程序中设置全局并行度, 因为如果在程序中对全局并行度进行硬编码,会导致无法动态扩容。这里要注意的是,由于 keyBy 不是算子,所以无法对keyBy 设置并行度。

2)提交应用时设置

在使用 flink run 命令提交应用时,可以增加-p 参数来指定当前应用程序执行的并行度, 它的作用类似于执行环境的全局设置:

bin/flink run –p 2 –c com.atguigu.wc.StreamWordCount
./FlinkTutorial-1.0-SNAPSHOT.jar

3)配置文件中设置

我们还可以直接在集群的配置文件 flink-conf.yaml 中直接更改默认并行度:

parallelism.default: 2

      这个设置对于整个集群上提交的所有作业有效,初始值为 1。无论在代码中设置、还是提交时的-p 参数,都不是必须的;所以在没有指定并行度的时候,就会采用配置文件中的集群默认并行度。在开发环境中,没有配置文件,默认并行度就是当前机器的 CPU 核心数。这也就解释了为什么我们在第二章运行 WordCount 流处理程序时,会看到结果前有 1~4 的分区编号——运行程序的电脑是 4 核CPU,那么开发环境默认的并行度就是 4。

 算子链

 

作业图

 

 

 

 

 

 

 

 

 

 

 

 

 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值