slot Task 任务连
概念有点多,有点乱,还待整理
Streaming dataflow
- Streaming dataflow = 一个应用程序
- Streaming dataflow = source + transformation + sink
Source :
- 流计算:可以使用来自消息队列或分布式日志(如 Apache Kafka 或 Kinesis)等流式源的实时数据。
- 批计算:可以使用来自各种数据源的有限的历史数据。
Sink:
- 应用程序生成的结果流可以发送到可以作为接收器连接的各种系统。
算子(Operator)
Flink 程序是并行和分布式的。
- a stream has one or more stream partitions
- each operator has one or more operator subtasks。
The operator subtasks are independent of one another, and execute in different threads and possibly on different machines or containers.
1.算子的并行度
算子子任务的数量 = 算子并行度;
下图所示,上面的是整体视角,下面的是经过并行度拆分后的视角
2.数据传输模式
有了并行度的概念后,我们需要了解,数据在两个算子之间是怎样传递的?上游的算子A并行度1中的数据是去下游算子B的并行度1还是并行度2呢?
两种传输模式:
- one-to-one (or forwarding) pattern
- redistributing pattern
1.One-to-one
streams (for example between the Source and the map() operators in the figure above) preserve the partitioning and ordering of the elements. That means that subtask[1] of the map() operator will see thesame elements in the same orderas they were produced by subtask[1] of the Source operator.
2.Redistributing streams
算子的每个子任务会将数据发送到下游算子的不同子任务中,具体如何传递依赖于传输规则:
- keyBy (which re-partitions by hashing the key)
- broadcast (广播)
- rebalance (which re-partitions randomly).
元素之间的顺序仅保留在每对发送subtask和接收subtask之间(例如,map() 的 subtask[1] 和 keyBy/window 的 subtask[2])
3. 并行度的设置
四种设置并行度方式的优先级
- 算子单独设置并行度(Operator.setParallelism())
- 代码全局设置(env.setParallelism)
- 提交job的时候设置 (或者web UI界面)
- 集群配置文件
算子的并行度如果没给,用全局,全局没给用命令行的,命令行也没给用配置文件的。
并行度优先级演示
Demo1:
public class Flink03_WordCount_Unbounded {
public static void main(String[] args) throws Exception {
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(4);//全局并行度4
ParameterTool parameterTool = ParameterTool.fromArgs(args);
String host = parameterTool.get("host");
int port = parameterTool.getInt("port");
//TODO 读取端口数据 无界流
DataStream<String> inputDataStream = env.socketTextStream(host, port);
KeyedStream<Tuple2<String, Integer>, Tuple> keyedStream = inputDataStream
.flatMap(new Flink01_WordCount_Batch.MyFlatMapFunc()).setParallelism(3)
.keyBy(0);
DataStream<Tuple2<String, Integer>> wordCountDataStream = keyedStream.sum(1).setParallelism(2);
wordCountDataStream.print("result"). setParallelism(1);
env.execute("asd");
}
}
- 配置文件中并行度是1
- Web 提交页面并行度设置:5
- 全局并行度4
- flatmap并行度为:3
- Sum()并行度为:2
- Print()并行度为1
Demo2
- 配置文件中并行度是1
- Web 提交页面并行度设置:5
- 全局并行度4
- flatmat并行度为:3
- Sum()并行度为:2
- Print()并行度不设置
说明了如果算子不单独设置并行度,就会按照全局的并行度来,依次往上推。
Flink中并行度和Spark中分区的理解
- Flink的并行度类似于Spark中的分区,不同的是flink基本所有算子都可以设置并行度,Spark只有部分算子可以设置分区数。注:flink中定义传输过程的算子是不能设置并行度的
- 对于Spark来说,是按照有shuffle的算子来划分阶段的,没有shuffle的算子即使设置的分区个数不同,也会合并到一个stage中,而且每个阶段的并行度(分区数)只取决于该阶段最后一个算子的分区个数。
- flink中如果算子指定了并行度不一样,那么会将这个算子单独算一个stage;所以flink的并行度是建立在算子的基础上,而Spark的分区是建立在stage的基础上。
任务链(Operator Chains)
1. 认识任务链
任务链就是将符合一定条件的算子合并成一个任务
在提交页面上看如下:一个蓝色框就是一个任务
一个任务就是一个Task
- 一个任务的一个并行度就是一个subTask
- 一个subTask就是一个线程,一个subTask占用一个slot;
下图中的示例数据流是用五个子任务执行的,因此有五个并行线程:
任务连减少了数据从线程到线程的切换和缓冲的开销,并在减少延迟的同时增加了总体吞吐量。
2. 算子合并成任务链的前提条件
多个算子符合如下条件可以被flink自动合并成任务链:
- 前后相连
- 并行度相同
- 前后关系为one to one
- 处在同一个共享组内
多个算子链接在一起形成一个任务,原来的算子成为任务的一部分;
链接的行为可以在编程API中进行指定:
- .disableChaining:不允许此算子加入任务链
- .startNewChain() 从当前算子开启任务链
- env.disableOperatorChaining() 全局禁止任务链化
测试
图中任务链有3个Task
- Task1和Task2因为并行度不同而不能合并;
- Task2和Task3因为不是one-to-one而不能合并;
如果将filter算子并行度改为3,变为下图
3. Task、线程、subTask
- 从计算逻辑上面看,最基本的计算单元就是一个算子,算子可以由其并行度拆成subTask,而一个subTask就是一个计算线程;
- flink对Task进行了优化,将多个算子合并成一个任务,一个任务 = 一个Task,这意味着将多个计算线程合并成一个计算线程
任务链和共享组的区别:
- 共享组:多个算子共享slots
- 任务链:将多个算子合并为一个大算子,其实是将subTask合并为一个Task
对比Spark:
Flink中的任务链相当于Spark中的Stage,Spark中的Stage的划分只需要满足OneToOne,一个stage中所有算子的并行度暴力合并,按照Stage中最后一个RDD的分区数作为当前stage中所有算子的并行度,所以spark中一般不设置算子的分区数,没屌用。
4. 任务链的优势
- 减少线程切换
- 减少序列化和反序列化
- 减少数据在缓冲区的交换
- 减少延迟提高吞吐力
缺点:
- 可能让N个比较复杂的业务跑在一个Slot中,本来一个业务就慢,如此更慢了,可以通过startNewChanin()或者disableChaining()或者全局禁用disableOperatorChaining()给分开
5. WebUI页面查看任务
- 在webUI页面中,一个蓝色的框表示一个任务
- 每个任务的并行度决定了当前任务的subTask个数
本质上来说,一个subTask就是一个计算单元,因此subTask叫做Task,一个任务有多个并行度,一个并行度对应一个Task
Flink任务执行图
一个subTask对应一个Thread
Slot 和 Slot共享组
什么是Slot?
- Slot是内存;
- 假如一个TaskManager有三个slot,那么它会将其管理的内存分成三份给各个slot;
需要注意的是,这里不会涉及到CPU的隔离,slot目前仅仅用来隔离task的受管理的内存。
为什么要将内存划分为slot?
(1)内存隔离
资源slot
化意味着一个subtask
将不需要跟来自其他job
的subtask
竞争被管理的内存
(2)Slot意味着TaskManager的并发能力
- 一个TaskManager进程的并行能力上限由Core个数限制
- Slot之间运行的任务是相互独立的,Slot个数 < Core,那么就是并行;
- Slot > Core ,Slot之间的任务就是并发;
Slot 共享组
共享组决定了哪些算子可以共享Slot,决定了SubTask能否在一个slot执行;
flink提供了两个实现类,用于设置共享组:
1.SlotSharingGroup
- 用于DataStreamAPI
- 用来实现slot共享的类,它尽可能地让subtasks共享一个slot
- 保证同一个group中并行度的相同的subtasks共享同一个slot
- 算子默认的group为default(即默认一个job下的subtask都可以共享一个slot)
- 为了防止不合理的共享,用户能通过API来强制指定operator的共享组(.slotSharingGroup(“xxx”) 指定共享组)
- 如果算子没有设置group,则其group和上游算子相同
- 适当设置group可以减少每个slot运行的线程数,从而整体减少机器负载
测试
测试环境
三台TaskManager;
每个TaskManager的slot个数为2,默认并行度设置为1;
Demo1
结果分析:当前job只需要一个slot,因为所有算子都属于同一个共享组(不设置,则属于默认的共享组)
Demo2
当前job用了4个slot;因为当前job的四个算子分别属于不同的共享组,不同的共享组的算子之间的slot是不能够共享的,所以1+1+1+1=4;
一个应用需要多少个Slot
一个job的slot的数量:每个共享组中最大并行度的和
任务链和共享组总结
- 一个Slot可以运行多个task
- 为了拓扑结构更加高先运行,flink提出了Chaining,尽可能的将operators 链接在一起作为一个Task
- 为了资源更充分利用,Flink又提出了SlotSharingGroup,尽可能让多个task共享一个slot
执行图(ExecutionGraph)
Flink 中的执行图可以分成四层:
- StreamGraph
- JobGraph
- ExecutionGraph
- 物理执行图
StreamGraph
- client端生成;
- 算子原封不动的流程图;
- 表示程序的拓扑结构。
JobGraph
- client端生成;
- 将多个符合条件的算子合并成一个任务链从而生成了JobGraph;
- 是提交给 JobManager 的数据结构。
算子合并成任务链:可以减少数据在节点之间流动所需要的序列化/反序列化/传输消耗。
ExecutionGraph
- JobManager端生成;
- ExecutionGraph是JobGraph的并行化版本;是调度层最核心的数据结构。
物理执行图
JobManager 根据 ExecutionGraph 对 Job 进行调度后,在各个TaskManager 上部署 Task 后形成的“图”,并不是一个具体的数据结构。
- StreamGraph:完全按照代码来,一个算子一个节点
- JobGraph: 根据共享组,合并算子
- ExecutionGraph:根据共享组内的并行度,将算子拆成多个Task