这里写目录标题
flink入门
二、快速上手
1、pom文件中添加的东西
1、里面有两个依赖,一个就是flink-scala,我们要用的是scala语言进行写代码。还有一个就是flink-streaming-scala,也就是流的问题。
– !! 上面那两个依赖的记忆方式: 用Scala写flink代码、用Scala的流式写flink代码。
2、还有一个打包插件,就是关于Scala代码进行打包,注意里面的版本问题导致的代码运行失败的话,那么就修改版本。
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_2.12</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.12</artifactId>
<version>1.10.1</version>
</dependency>
</dependencies>
<!-- 下面是打包插件 -->
<build>
<plugins>
<!-- 该插件用于将Scala 代码编译成class 文件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<!-- 声明绑定到maven 的compile 阶段 -->
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2、 添加scala文件夹
标记为源文件目录
然后就可以在下面创建对应的Scala的class。
如果要创建一个可执行的程序的话,那么不要创建类,创建一个单例对象。
3、批处理代码
批处理的话,需要一个提前准备好的文件,从这个文件中读取数据,也就是需要一个数据文件。
创建一个文本文件。
文本中添加内容
hello
thanks
yes ok
shanghai
beijing
hello
shanghai
shanghai
yes ok
代码
把scala下面的对应的隐式转换引入。
import org.apache.flink.api.scala._ //把这个放到代码上面。
package com.atguigu.wc
import org.apache.flink.api.scala.ExecutionEnvironment
import org.apache.flink.api.scala._
object wordcount {
def main(args: Array[String]): Unit = {
//创建一个批处理
//创建执行环境,因为当前是分布式处理,并不是一个单机的程序,直接读进来。现在是分布式的环境。所以必须创建执行环境,也就是类似于上下文。
val env = ExecutionEnvironment.getExecutionEnvironment
// 从文件中读取数据。将本地文件的地址作为一个字符串变量。
val inpath = "D:\\code_put\\flink_tuturial\\src\\main\\resources\\hello.txt"
//当前是批处理,那么定义一个dataset,基于当前的执行环境调用一个方法。要进行读文件。
//输入的变量是代表本地文件地址的变量。
val inputDataSet = env.readTextFile(inpath)
//对数据进行转换处理统计
//先分词,然后按照Word进行分组,最后进行聚合统计。
val resultDataSet = inputDataSet
.flatMap(_.split(" "))
.map((_, 1))
.groupBy(0)
.sum(1)
//打印输出
resultDataSet.print()
}
}
Scala的风格,就是先写变量然后写方法名,也就是变量.方法名,然后得到一个新的变量。
inputDataSet就理解为类似于set,文本文件作为一个string类型的变量,作为dataset类型肯定是里面只有一个元素,也就是初始的文本。
用flatmap进行改变结构。然后进行切割之后,于是就有了很多的元素,也就是一个存放string类型的集合,里面多了一些元素。
对集合中的每个元素都变成子集合,也就是(xxx,1),这种形式。也就是此时的dataset[(string,int)]就是 ( (xxx,1),(xxx,1),(xxx,1),(xxx,1)…(xxx,1) )。
flatMap = map + flatten
就按照上面的例子,inputDataSet代表的是一个string类型的字符串,如果用map 进行切割操作的话,将会得到的是 ((xxx),(xxx),(xxx),…(xxx)),也就是集合里面包含了一个一个小的集合,这就是map得到的隐射结果。接着flat的话,就会成为一个大的集合,里面的小的集合就拼接在一起了,就是进行一个拼接。(xxx,xxx,xxx,xxx…xxx)
如果是用flatMap 的话,就是直接转换成 (xxx,xxx,xxx,xxx…xxx)。
4、流处理代码
流处理的特点: 有头没尾,源源不断的进行处理,来一个处理一个,低延迟,实时处理结果。
还是在上面相同的包下面,创建一个新的class,用来演示流处理。
package com.atguigu.wc
import org.apache.flink.streaming.api.scala._
//注意这里是流的,隐式转换也要有streaming。
object StreamWordCount {
def main(args: Array[String]): Unit = {
//创建执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
//创建一个socket文本流
val inputStream = env.socketTextStream("hadoop102", 7777)
val value = inputStream.flatMap(_.split(" ")).filter(_.nonEmpty).map((_, 1)).keyBy(0).sum(1)
value.print()
//启动任务
env.execute("12345") //可以给当前的作业起一个名字,是一个字符串的形式。
}
}
开启hadoop102这个虚拟机,然后用xshell连接,然后输入 nc -lk 7777
然后就立刻就会出现这个计算结果。
并行子任务&&并行度的设置
前面的小数子的含义是:flink本身是一个分布式的架构,当前执行的时候,默认是可以做并行执行的。这里的数字表示在哪个并行的子任务上,也就是并行子任务的序号。这个默认的数字就是当前机器的默认核数。hadoop102这个虚拟机设置的核数的时候就是8,那么会出现1、2、3、4、5、6、7、8这八个数字。
针对当前的执行环境,然后可以设置一个并行度,也就是设置配置并行的线程。
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.setParallelism(2) //紧跟着环境的位置。这里是属于全局设置。
那么每个对应的输出执行在哪个线程上呢??
按照当前Word的hash值去做分配。
可以观察到,同一个单词,肯定是在同一个线程中的,线程编号都是同一个。
其实主要是出在keyby上,代码中做了keyby处理。进行做分组,在并行试架构中是怎么做分组的呢?
map处理完,后面并行的也有很多个sum,要进行分组,也就是每个map出来的数据要分发到哪个sum中呢?那么keyby是怎么分组的呢??
同一个key的话,就让它放到同一个sum任务中进行做统计。也就是上游同一个key的不同任务发送到下游的同一个任务中去。这个过程就是相当于一个数据重分区的过程。是基于当前key的hashcode,但是并不完全是以hashcode作为分区号,我们知道求hash的那个值很大的,提供的并行任务就是那么几个,自己设置的就是8个。最后有一个类似取余的过程。最后就得到一个并行子任务的编号。
同一个key的数据来了之后,经过hash运算,然后经过取模,总会分配到同一个子任务中去。不同的key也可能分发到同一个子任务中,毕竟可能hash值相同。
算子设置并行度
每个算子每个任务都是可以认为是独立的。
val value = inputStream.
flatMap(_.split(" ")). //默认是8个并行度
filter(_.nonEmpty).
map((_, 1)).setParallelism(3). // 这里map设置的并行度是3
keyBy(0).
sum(1).setParallelism(2) //这里sum设置的并行度是2
动态设置端口的参数
点击 —编辑配置。
在这个位置进行输入
–host hadoop102 --port 7777
注意的是–与host和port之间不能有间隔。
如下所示,然后点击apply,点击OK。
代码部分
package com.atguigu.wc
import org.apache.flink.api.java.utils.ParameterTool
import org.apache.flink.streaming.api.scala._
//注意这里是流的,隐式转换也要有streaming。
object StreamWordCount {
def main(args: Array[String]): Unit = {
//创建执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
val parameter = ParameterTool.fromArgs(args)
val host = parameter.get("host")
val port = parameter.getInt("port")
//创建一个socket文本流
val inputStream = env.socketTextStream(host,port)
val value = inputStream.
flatMap(_.split(" ")).
filter(_.nonEmpty).
map((_, 1)).
keyBy(0).
sum(1)
value.print()
//启动任务
env.execute()
}
}
打印时设置并行度为1
因为是并行的,所以是几乎同时进入当前这个系统中,后面进行传输的过程中,到达不同子任务对应的分区,然后就会导致乱序。
分布式架构带来的数据乱序,哈哈,可能说全局并行度设置为1(也就是在执行环境之后,立马设置全局并行度),所有的都是一条线,那肯定是有序的,但是这对于高吞吐的分布式架构,有什么意义呢??没意义。高并发和高吞吐就做不了了。
代码
package com.atguigu.wc
import org.apache.flink.api.java.utils.ParameterTool
import org.apache.flink.streaming.api.scala._
//注意这里是流的,隐式转换也要有streaming。
object StreamWordCount {
def main(args: Array[String]): Unit = {
//创建执行环境
val env = StreamExecutionEnvironment.getExecutionEnvironment
val parameter = ParameterTool.fromArgs(args)
val host = parameter.get("host")
val port = parameter.getInt("port")
//创建一个socket文本流
val inputStream = env.socketTextStream(host,port)
val value = inputStream.
flatMap(_.split(" ")).
filter(_.nonEmpty).
map((_, 1)).
keyBy(0).
sum(1)
value.print().setParallelism(1)
//启动任务
env.execute()
}
}
发现前面的子任务的编号没有了,因为最后打印这一步的并行度是1,也就是最后一步只有一个线程。
有一些需要外部文件写入的操作,最后也得设置并行度是1,往同一个文件写入,防止发生错误。
三、flink架构的部署
1、flink的安装与修改配置文件
一)、提前准备好的flink的压缩包
进入到software文件夹中,然后将这压缩包拖进去。
然后进行解压缩,将解压后的文件放到module文件夹中。
[atguigu@hadoop102 software]$ tar -zxvf flink-1.10.1-bin-scala_2.12.tgz -C /opt/module/
然后 进入到module中
[atguigu@hadoop102 opt]$ cd software/
[atguigu@hadoop102 software]$ cd …/module/
[atguigu@hadoop102 module]$ ll
解压后的文件
然后进入flink-1.10.1的bin目录下
[atguigu@hadoop102 module]$ cd flink-1.10.1/
[atguigu@hadoop102flink-1.10.1]$ cd bin
[atguigu@hadoop102 bin]$ ll
二)、修改conf中yaml的参数,设置jobmanager是哪个节点
然后进入flink-1.10.1的conf目录下
[atguigu@hadoop102 bin]$ cd …/conf
[atguigu@hadoop102 conf]$ ll
打印日志有两种格式
我们需要把yaml文件进行修改。
jobmanager.rpc.address: hadoop102 //配置jobmanager
taskmanager.numberOfTaskSlots: 2 //生产环境中根据资源进行修改。
然后进行关闭。
三)、修改conf中slaves的参数,填写所有的节点名称
也就是哪些节点参与分布式架构之中。
把机器的节点输入进去。
四)、将flink的文件夹分发到集群其他的节点
[atguigu@hadoop102 conf]$ cd …/
[atguigu@hadoop102 flink-1.10.1]$ cd …/
[atguigu@hadoop102 module]$ xsync flink-1.10.1/
2、standalone模式
1)、启动
在flink的目录下,进行启动
[atguigu@hadoop102 flink-1.10.1]$ bin/start-cluster.sh
启动起来以后,就可以在网页上输入了。
hadoop102:8081
这里我们可以看到之前我们设置的东西
taskmanager的个数是3,也就是之前在slaves中设置的3台机器节点,也就是flink集群中有几个节点。
total task slots的个数是6,因为我们设置单个的taskmanager的slot的个数是2,集群中有3个节点,所以个数是6个。
Available Task Slots的个数是6,因为现在还没有运行任务,处于闲置状态,所以是6个。
点开里面就有各个节点的信息。jobmanager的,以及taskmanager的节点信息。
2)、页面提交job
编译
打包
这个jar包是我们要的。复制到桌面上,然后上传的时候直接在桌面上进行上传该jar包。
上传jar包
点击这个jar包
复制一下包名加类名
点开执行计划,可以看一下。
读取数据源的并行度默认就是1,这个算子默认的并行度就是1。这个是算子级别的并行度设置,只不过是默认的设置。
最后一个位置sink的并行度也是1,因为我们在代码中进行设置了。
在代码中,其他位置我们没有设置并行度,那么就以提交job页面的那个设置的并行度为主。如果提交页面也没有,那么就以配置文件中的设置为主。(因为这是运行环境,并不是开发环境,如果是开发环境的话,默认的话就是CPU核数。)
其实关于并行度的问题
算子的并行度 > 环境设置的并行度(代码中全局设置) > 提交job页面设置的并行度 > 配置文件中的并行度
解释:环境设置并行度,这里设置的是代码中每个算子的默认并行度。
关于环境设置的并行度
开发环境中: 默认的话是cpu核心数 。
生产集群运行环境中: 如果每个算子没有单独设置并行度,而且代码的全局也没有设置并行度,那么就以提交job的时候,设置的并行度为准。
在提交之前先把端口打开,然后再在job提交页面进行submit。
然后输入一些数据
因为是在taskmanager中进行计算的(main代码中的每一步是如何定义的,是在jobmanager中做的,数据来了真正执行是在taskmanager中执行的),所以我们要点开taskmanager进行观察。
点开进去之后,然后点开stdout,然后就可以看见数据了。
3)、命令行提交job
将jar包放到一个地方中,然后写上这个路径。
[atguigu@hadoop102 bin]$ ./flink run -c com.atguigu.wc.StreamWordCount -p 2 /opt/module/save_jars/flink_tutiorial-1.0-SNAPSHOT-jar-with-dependencies.jar
–host hadoop102 --port 7777
在端口中输入数据。
然后看到一个任务在运行。
数据所在的节点(就是free slots比all slots要少,说明正任务正在这个节点中进行运行)
看到了数据。
命令行的常见命令
查看正在运行的任务id
先进入到flink的bin目录下,显示的那一串数字夹带字母的就是任务id
[atguigu@hadoop102 bin]$ ./flink list
Waiting for response…
------------------ Running/Restarting Jobs -------------------
08.09.2021 17:30:51 : fff1b437f0026f72d65d99941b9ebe9c : Flink Streaming Job (RUNNING)
-------------------------------------------------------------- No scheduled jobs.
停掉正在运行的任务
先进入到flink的bin目录下,然后执行命令,cancel后面是任务id。
[atguigu@hadoop102 bin]$ ./flink cancel fff1b437f0026f72d65d99941b9ebe9c
看已经运行完的、失败的、正在运行的任务
> [atguigu@hadoop102 bin]$ ./flink list -a
> Waiting for response... No
> running jobs. No scheduled jobs.
> ---------------------- Terminated Jobs -----------------------
> 08.09.2021 15:47:08 : 8499b0037fff99110b710301954aed9e : Flink Streaming Job (CANCELED)
> 08.09.2021 17:30:51 : fff1b437f0026f72d65d99941b9ebe9c : Flink Streaming Job (CANCELED)
> --------------------------------------------------------------
关闭掉集群
taskmanager和jobmanager都关掉了。
[atguigu@hadoop102 bin]$ ./stop-cluster.sh
Stopping taskexecutor daemon (pid: 2639) on host hadoop102.
Stopping taskexecutor daemon (pid: 2176) on host hadoop103.
Stopping taskexecutor daemon (pid:2167) on host hadoop104.
Stopping standalonesession daemon (pid: 2256) on host hadoop102.
3、Yarn模式(生产环境中常见)
yarn的这种模式,是属于容器化的资源管理平台。
以 Yarn 模式部署 Flink 任务时, 要求 Flink 是有 Hadoop 支持的版本, Hadoop 环境需要保证版本在 2.2 以上, 并且集群中安装有 HDFS 服务。
之前standalone模式的话,需要直接需要bin/start-cluster.sh。部署在yarn上的话,那么这个flink集群就是由yarn来启动。
Session-cluster模式
Session-cluster模式,也叫会话模式。
先在yarn上创建一个yarn-session,然后通过yarn-session先启动一个flink集群。然后就在这个集群里面提交作业。提交在一个yarn-session里面的job,特点就是资源是共享的,适合规模比较小的,执行时间有限的作业。
这种就是相当于一堆job放在一个集群上面去运行,有点类似于standalone模式。
准备工作
将下面这个jar包,扔到 /opt/module/flink-1.10.1/lib 里面。
如果缺少这个包的话,会有如下的错误爆出。
启动
1、启动hadoop集群
2、启动 yarn-session (相当于此时启动一个flink集群)
[atguigu@hadoop102 module]$ cd flink-1.10.1/
[atguigu@hadoop102flink-1.10.1]$ cd bin
[atguigu@hadoop102 bin]$ ll
./yarn-session.sh -s 4 -jm 1024 -tm 1024 -nm test -d
然后就是跳出一个web网址。这个网址是打开flink的web页面。
其中:
-jm: JobManager 的内存( 单位 MB)。
-tm: 每个 taskmanager 的内存( 单位 MB)。
-nm: yarn 的 appName(现在 yarn 的 ui 上的名字)。
-d: 后台执行。
提交命令行的优先级是高于配置文件中的设置,显然这里是起作用了。
都是动态的。在提交任务之前都是0。
3、提交job
就按照之前命令行提交job的方式一模一样。
./flink run -c com.atguigu.wc.StreamWordCount
/opt/module/save_jars/flinkTutorial-1.0-SNAPSHOT-jar-with-dependencies.jar --host hadoop102 --port 7777
提交之后就看到任务的id。
提交之后就动态的提供了slots数目和taskmanager的数目。
也可以在web页面中看到任务id。
4、去yarn 控制台查看任务状态
hadoop103:8088
就可以看到任务id
application_1632635957937_0001
在开启yarn-session的时候,就能看到对应的任id。
5、取消yarn-session
yarn application --kill application_1632635957937_0001
这样的话,就是关闭了yarn-session。变成了history,也就是成为了历史,也就是关闭了。
per-job模式
这种模式并不会一开始就创建集群,而是yarn中等待提交job。job提交完之后,然后按照当前的每一个job,去创建一个flink集群。这个过程就是相当于每个job独占一个flink集群。不同的job之间彼此互不影响,都是独占的,只要yarn有资源,等待yarn的分配,然后拿过来就用。
单独一个作业,一个集群。适合长时间运行不结束的作业。
启动
1、启动hadoop集群
2、直接启动job
./flink run –m yarn-cluster
-c com.atguigu.wc.StreamWordCountFlinkTutorial-1.0-SNAPSHOT-jar-with-dependencies.jar --host hadoop102 --port 7777
./flink run 表示要进行启动
–m yarn-cluster 根据yarn给当前的job起一个集群,也就是per-job模式。
-c com.atguigu.wc.StreamWordCountFlinkTutorial-1.0-SNAPSHOT-jar-with-dependencies.jar 打包的类名
--host hadoop102 --port 7777 机器号以及端口号
四、Flink 运行架构
1、Flink 运行时的组件
jobmanager
jobmanager就是作业管理器,接受提交上来的打包好的代码jar包。然后分析里面的处理流程。然后生成一个最后可以执行的执行图。然后将作业分成每个可以执行的task,然后分发给taskmanager。
还会负责协调做检查点,checkpoint。
taskmanager
taskmanager就是任务管理器,具体干活的就是taskmanager,要进行计算。
每一个taskmanager里面的计算资源,主要是内存和CPU,划分的时候,CPU是没有办法隔离的。
所以对于taskmanager划分资源,主要是对内存进行一个隔离处理,每一部分就叫做一个slot,slot是taskmanager里面最小化的资源单位。
resourcemanager
resourcemanager就是资源管理器,主要是管理整个集群的资源,集群里面的资源就是slot。
资源管理器其实就是资源的调度,资源就是taskmanager,一个job任务,需要多少个TaskManager中的slot,然后就去资源管理器去申请,看看剩余空闲的slot数目能不能满足此时的job。
我们知道flink可以同时启动多个任务,也就是此时机器中的slot不一定有空闲。
所以jobmanager就要去资源管理器去看看taskmanager有没有空闲的slot,来支持这个job的进行。
那么这里提一下slot,taskmanager中设置几个slot比较好呢?
核数0.33,也就是本台机器是8核的话,设置为80.33 向上取整为3,也就是本台机器的slot数目为3个。
2、Flink 任务提交流程
3 、Flink 任务调度原理
taskmanager与slot
首先,我们要知道的是,一个算子就是一个task。所谓的subtask是因为这个算子的并行度并不是1,并行的子任务就是subtask。
每个taskmanager都是一个jvm进程,那么就是会独立的进行执行一个或者是多个subtask。
taskmanager含有一定的资源,这个资源就是slot。资源slot化意味着内存的隔离,没必要不同job之间的slot的竞争。
那么一个taskmanager设置几个slot呢?
这个有人说是 至少是: 核数033,也就是机器是8核的话,那么80.33向上取整得到3个slot。
这个设置几个是需要实践,就好比打个比方: 在门的总宽度 (内存)一样的前提下,一栋楼是开一扇大门(少量的slot),还是开多个小门(较多的slot),才能使人流流动的比较快呢?这显然是不能直接判断的,这个是需要实践之后才能判断的。
默认情况下, Flink 允许子任务共享 slot, 即使它们是不同任务的子任务( 前提是它们来自同一个 job)。 这样的结果是, 一个 slot 可以保存作业的整个管道。
task slot是静态的概念,是指的是taskmanager具有的并发执行的能力。
slot的数量其实是taskmanager内存划分的个数,划分成多少个,这就相当于决定了一个taskmanager同时可以并行的执行多少个task。也就是通过slot的数量可以控制当前taskmanager并行处理的能力。
这是一个静态的能力。
slot与并行度
在执行过程中, 一个流( stream) 包含一个或多个分区( stream partition), 而每一个算子( operator) 可以包含一个或多个子任务( operator subtask), 这些子任务在不同的线程、不同的物理机或不同的容器中彼此互不依赖地执行。
一个特定算子的子任务( subtask) 的个数被称之为其并行度( parallelism) 。一般情况下, 一个流程序的并行度, 可以认为就是其所有算子中最大的并行度。一个程序中, 不同的算子可能具有不同的并行度。
其中最大的算子的并行度就是这个流的并行度,也就是最后至少要用几个slot,是这里决定的。
如图,这个里面的算子中最大的并行度是2,所以任务的运行需求的slot至少是2个,任务才能进行。
设置并行度
第一个虚线上面: 当前资源的数量。推荐的配置是: 该台机器的CPU核数=该台机器的slot的数目。
第二个虚线上面: 默认情况下,啥并行度都不设置(代码以及提交的时候也没有),那么就是集群配置文件里面的定义的默认并行度。默认是1。这种就是大多数的资源没有利用起来,效率低。
第三个虚线上面: 更改配置文件里面的默认并行度 ; 或者是提交job的时候, - p这个参数设置为2 ; 或者是在web UI的界面上,是一样的原理;还有就是在代码中设置,全局设置并行度 或者 是每一个算子单独设置并行度。
优先级是: 范围越小,优先级越高;代码中设置的话,会覆盖掉所有的默认配置。
次之,就是提交任务时候的 -p 参数,次之,(生产环境)用集群的配置文件中的配置 (开发环境下,idea执行的时候,啥也不设置的时候,默认并行度就是,当前的运行机器的CPU核数)。
第一个虚线上面: 代码中全局设置为9,所有的slot里面,满满的排满每一个任务。因为是全局并行度是9.
第二个虚线上面: 适合文件输出,sink并行度设置为1比较安全。因为在并行的执行写出操作的时候,有可能有写入冲突的问题。sink只在一个slot中出现。
所有的任务做完sink之前的那一步,其他的taskmanager最后都要传到taskmanager3的slot中的sink任务,让它做输出操作。
数据传输形式
Stream 在算子之间传输数据的形式可以是 one-to-one(forwarding)的模式也可以是 redistributing 的模式, 具体是哪一种形式, 取决于算子的种类。
One-to-one:stream(比如在 source 和 map operator 之间)维护着分区以及元素的顺序。那意味着 map 算子的子任务看到的元素的个数以及顺序跟 source 算子的子任务生产的元素的个数、顺序相同, map、fliter、flatMap 等算子都是 one-to-one 的对应关系。
类似于 spark 中的窄依赖
Redistributing: stream(map()跟 keyBy/window 之间或者 keyBy/window 跟 sink 之间)的分区会发生改变。每一个算子的子任务依据所选择的 transformation 发送数据到不同的目标任务。例如,keyBy() 基于 hashCode 重分区、broadcast 和 rebalance 会随机重新分区,这些算子都会引起 redistribute 过程,而 redistribute 过程就类似于Spark 中的 shuffle 过程。
类似于 spark 中的宽依赖
图中,算子出现几次,说明并行度就是几。
1、source到flatmap的时候,并行度发生了变化,因此不能合并。
2、flatmap到agg,虽然并行度都是2,但是有key by,基于hashcode进行了重分区,这里不是one to one的操作,所以不能合并。
3、agg、sink的并行度都是2,且没有发生重分区的操作,因此是可以合并任务的。
并行子任务如何做分配?
根据执行计划图,算子A的并行度是4,那么分配4个slot中;算子B的并行度是4,那么分配4个slot中;算子C的并行度是2,那么分配2个slot中;算子D的并行度是4,那么分配4个slot中;算子E的并行度是2,那么分配2个slot中。
默认分配在哪个taskmanager中是随机的。
1、怎么实现并行计算??
代码中每一个算子调用,后面都可以直接设置一个并行度。设置并行度之后,就说明当前的算子可以并行执行。
2、流处理中需要多少个slot??
因为slot共享机制的存在,就是以算子中最大的并行度作为标准就可以了。把最大算子的并行度作为流处理的并行度,也就是至少要分配的slot数量。
3、job中的task数目和 job需要的slot个数的关系??
代码中的算子的task数目之和和slot的个数没有关系。
代码中每一个(task)算子中的最大并行度和slot的数目有关系,二者是因为前者需要那么多,所以后者提供那么多,也就是二者是相等的关系。
程序与数据流
所有的 Flink 程序都是由三部分组成的:Source 、Transformation 和 Sink。
Source 负责读取数据源, Transformation 利用各种算子进行处理加工, Sink 负责输出。
在运行时,Flink 上运行的程序会被映射成“ 逻辑数据流”( dataflows),它包含了这三部分。每一个 dataflow 以一个或多个 sources 开始以一个或多个 sinks 结束。dataflow 类似于任意的有向无环图( DAG)。在大部分情况下, 程序中的转换运算( transformations) 跟 dataflow 中的算子( operator) 是一 一对应的关系, 但有时候, 一个 transformation 可能对应多个 operator。
执行图
Flink 中的执行图可以分成四层:StreamGraph -> JobGraph -> ExecutionGraph ->
物理执行图。
StreamGraph : 是根据用户通过 Stream API 编写的代码生成的最初的图。用来表示程序的拓扑结构。 (这里是最初的,根据代码,直接生成的一个算子一个task,也就是想象成流中一个算子指向下一个算子。)
JobGraph:StreamGraph 经过优化后生成了 JobGraph,提交给 JobManager 的数据结构。主要的优化为,将多个符合条件的节点 chain 在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。
(在客户端提交之前,对代码做一些合并优化,然后去进行提交。)
ExecutionGraph : JobManager 根 据 JobGraph 生 成 ExecutionGraph 。
ExecutionGraph 是 JobGraph 的并行化版本, 是调度层最核心的数据结构。
(在jobmanager中,按照我们定义的并行度,然后把每一个任务进行拆开,任务之间的传输方式定义清楚,相当于现在是一个真正的可执行的任务,从哪个任务来到哪个任务去。)
物理执行图: JobManager 根据 ExecutionGraph 对 Job 进行调度后, 在各个
TaskManager 上部署 Task 后形成的“ 图”, 并不是一个具体的数据结构。
(jobmanager中的图分发发给各个taskmanager,taskmanager按照jobmanager中的图,进行执行。)
任务链
相同并行度的 one to one 操作,Flink相连的算子(task),链接在一起形成一个 task,原来的算子成为里面的一部分。将算子链接成 task 是非常有效的优化: 它能减少线程之间的切换、减少基于缓存区的数据交换 (上下游算子传递数据的时候,序列化与反序列化的开销), 在减少时延的同时提升吞吐量。链接的行为可以在编程 API 中进行指定,也就是在代码中进行指定。
全局禁止任务的合并
全局禁止任务链的合并,也就是一个task就是一个任务,不会再进行任务的合并。
某个任务合并的任务链都断开
当前的算子直接摘出来,禁止与前后的算子进行合并任务链。也就是前面也要断开,后面也要断开。
执行图的效果。不参与任务链的合并,前后都得断开。这样的话,任务数目就变多了。
某个任务合并的任务链从某个算子位置断开
任务链前后可以合并,中间拆开。想把一个任务断开成两个任务。
想在filter和map之间断开。
这样就开启一个新的任务链。
就是这个样子。从这个位置断开,成为两个任务。
某个算子单独使用一个slot
----- 某个算子开始属于不同的共享组
从某个算子之后都是一个共享组a,这个算子之前的是一个共享组。在同一个共享组之内的所有操作,他们可以共享slot。不同共享组之间的任务一定是分配在不同的slot中。
------ 某一个算子单独属于一个共享组,前后的算子还是属于一个共享组。
如果某个算子,想单独使用一个slot的话,也就是slot不进行共享,那么这个算子的前后算子的共享组是同一个就是。这种方式会导致需要使用的slot数目,这种就是:
每个共享组中的slot数目之和,就是启动任务所需要的slot数目。
总之,一个任务是不是能启动起来,就是看代码里面的算子中最大的并行度与可以利用的slot的数目的比较,如果是小于等于可以利用的slot的数目,那么是可以启动起来这个任务的;如果不是的话,那么就启动不起来。
flink任务调度原理
Flink program
根据代码(program code ),生成一个逻辑上的数据流图(datagflow graph),然后利用一个客户端,将这些东西发送出去。客户端可能是命令行也可能是web UI,反正是有这样一个接口,进行发送。
在发送的时候,将数据流图(datagflow graph)做一个简单的调整,有一些可以合并的操作,然后就合并在一起。所以在提交job之后,在运行的时候,会发现有一些任务是合并在一起的。
客户端将jar包,以及数据流图等。提交给jobmanager。
Jobmanager
jobmanager分析数据流图,分析并行度是几,每个任务有几个并行的子任务。然后把它拆开,然后就可以知道当前有多少个任务,然后判断有多少个slot,然后怎么分配。去resourcemanager申请资源,申请到资源之后。在图中,有两个taskmanager可以提供资源,每个taskmanager本身是有3个slot,这是一个静态的能力,也就是最多可以提供3个slot,来执行并行的任务。
图中需要4个slot去执行任务,jobmanager做了一个调度,分配了两个worker,上的两个slot。jobmanager给taskmanager传的是部署,取消操作。这些是由jobmanager发出指令到taskmanager,另外会做checkpoint存盘的机制。
taskmanager
与jobmanager实时返回心跳信息,做实际的任务计算。
疑问: 啥是开发环境? 啥是生产环境?
那个开发环境是idea连上了虚拟机的某一个节点。生产环境是打包上传到集群上。