文章大部分数据来源 : https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/concepts/flink-architecture/
Flink 是一个分布式系统,需要有效的分配和管理计算资源才可以执行流式程序;
集成了常见的资源管理器如 YARN,K8S;也可以设置为作为独立集群甚至库运行
程序运行会有一下步骤
Flink 运行时由两种类型的进程组成:一个JobManager和一个或多个TaskManager。
Client不是运行时和程序执行的一部分,而是用于准备数据流并将其发送到 JobManager *。*之后,客户端可以断开连接(分离模式),或者保持连接以接收进度报告(附加模式)。客户端作为触发执行的 Java/Scala 程序的一部分运行,或者在命令行进程中运行./bin/flink run ...
。
JobManager 和 TaskManager 可以通过多种方式启动:直接在机器上作为独立集群启动,在容器中启动,或由[YARN]等资源框架管理。TaskManager 连接到 JobManage,并分配工作。
负责协调Flink 应用程序去分布式执行:负责安排任务的执行,已完成的任务做出反应,协调检查点,协调故障恢复;这些功能点有下面三和部分处理
ResourceManager:负责 Flink 集群中的资源取消/分配和供应——它管理任务槽,这是 Flink 集群中的资源调度单位;Flink为不同的资源环境(YARN,K8S,单机部署) 实现了多个ResourceManager,独立部署的时候,无法自行启动新的TaskManager
Dispatcher:Dispatcher提供了一个 REST 接口来提交 Flink 应用程序执行,并为每个提交的作业启动一个新的 JobMaster。它还运行 Flink WebUI 以提供有关作业执行的信息。
JobMaster:JobMaster负责管理单个 JobGraph[的]执行。Flink 集群中可以同时运行多个作业,每个作业都有自己的 JobMaster。
JobManager 最少部署一个,也有高可用的部署方式,部署多个JobManager HA 模式,但是只有一个是leader
TaskManagers (也称为workers)执行数据流的任务,并缓冲和交换数据流。
必须始终至少有一个 TaskManager。TaskManager 中资源调度的最小单位是任务槽(Slot)。TaskManager 中任务槽的数量表示并发处理任务的数量。请注意,多个运算符可以在一个任务槽中执行
对于分布式执行
图上面部分:
三个Task ,每个Task ,都只有一个subTask ,就是并行度都是1
图下面部分:
三个Task ,第一个Task 并行度2,第二个Task并行度2,第三个并行度是1,五个并行的线程
Flink 提供相关API 来组合算子链或断开算子链
每一个TaskManager 也就是 workers ,都是一个JVM 进程;TaskManager 其内部有不同的线程,每个线程执行的是 一个任务(并行度1)或者子任务(并行度>1);为了控制 TaskManager 接受的任务量.每个TaskManager 有一个任务槽的概念;
每个任务槽代表着TaskManager 的固定资源,比如说是有三个任务槽的TaskManager,每个 TaskManager 进程会管理内存,然后每个1/3 对应每个任务槽的内存大小,目前只有内存隔离,没有CPU 隔离;
拥有多个槽意味着更多的子任务共享同一个 JVM。同一个 JVM 中的任务共享 TCP 连接(通过多路复用)和心跳消息。它们还可以共享数据集和数据结构,从而减少每个任务的开销
默认情况下,Flink 允许子任务共享槽,即使它们是不同任务的子任务,只要它们来自同一个作业Job(相同任务Task不能放在同一个槽位)。结果是一个槽可能容纳整个作业流水线。允许此插槽共享有两个主要好处
每个槽位的keyBy window().apply 的数据可以来源于 上一个source map的数据 ,容纳整个作业流水线;
注:job 中并行度最大的Task 的(也就是subTask 个数) <= 可用槽位数
Flink 应用程序是从其方法生成一个或多个 Flink 作业的任何用户程序main()
。这些作业的执行可以发生在本地 JVM ( LocalEnvironment
) 中,也可以发生在具有多台机器的远程集群设置 ( RemoteEnvironment
) 中。对于每个程序,都ExecutionEnvironment
提供了控制作业执行(例如设置并行度)和与外界交互的方法;
Flink Application 的作业可以提交到
这些选项之间的区别主要与集群的生命周期和资源隔离相关
main()
方法在集群而不是客户端上运行。作业提交是一个一步的过程:你不需要先启动一个 Flink 集群,然后再将作业提交到现有的集群会话中;相反,您将应用程序逻辑和依赖项打包到一个可执行作业 JAR 中,集群入口点 ( ApplicationClusterEntryPoint
) 负责调用main()
提取 JobGraph 的方法。例如,这允许您像在 Kubernetes 上部署任何其他应用程序一样部署 Flink 应用程序。因此,Flink Application Cluster 的生命周期与 Flink Application 的生命周期相关联。分区算子:用于指定上游Task d的各个subTask 和下游Task 的各个subTask 的数据是如何传输的
Flink 中,对于上下游subTask 之间的数据传输控制,由ChannelSelector策略来控制,而且Flink内针对各种场景,开了了不同的ChannelSelector 实现(也对应下面的发送类型)
ChannelSelector (org.apache.flink.runtime.io.network.api.writer)
OutputEmitter (org.apache.flink.runtime.operators.shipping)
RoundRobinChannelSelector (org.apache.flink.runtime.io.network.api.writer)
StreamPartitioner (org.apache.flink.streaming.runtime.partitioner)
BroadcastPartitioner (org.apache.flink.streaming.runtime.partitioner)
CustomPartitionerWrapper (org.apache.flink.streaming.runtime.partitioner)
ForwardPartitioner (org.apache.flink.streaming.runtime.partitioner)
GlobalPartitioner (org.apache.flink.streaming.runtime.partitioner)
KeyGroupStreamPartitioner (org.apache.flink.streaming.runtime.partitioner)
RebalancePartitioner (org.apache.flink.streaming.runtime.partitioner)
RescalePartitioner (org.apache.flink.streaming.runtime.partitioner)
ShufflePartitioner (org.apache.flink.streaming.runtime.partitioner)
设置数据传输策略,不需要显示的指定partitioner,调用封装好的即可;没有指定,底层会自己决定用哪个传递数据
定义算子发送数据到下一个算子的发送类型 | 描述 |
---|---|
dataStream.global(); | 全部发送到第一个 |
dataStream.broadcast(); | 广播,下游每个都发送 |
dataStream.forward(); | 并发度一样时,一对一发送 |
dataStream.shuffle(); | 随机均匀分配 |
dataStream.rebalance(); | 轮流分配 Round-Robin |
dataStream.rescale(); | 本地轮流分配 Local Round-Robin ==> 分组后轮下 |
dataStream.partitionCustom(); | 自定义广播 |
dataStream.keyBy() | 数据key HashCode 分配 |
写一个案例
public class _01_PartitionStream {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
configuration.setInteger("rest.port", 8822);
// 获取环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);
DataStreamSource<String> dataStreamSource = env.socketTextStream("192.168.141.141", 9000);
DataStream<String> map1Ds = dataStreamSource.map(x -> "demo" + x).setParallelism(12);
DataStream<String> flatMapDS = map1Ds.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out) throws Exception {
String[] split = value.split(",");
for (String s : split) {
out.collect(s);
}
}
}).setParallelism(2);
DataStream<String> map2Ds = flatMapDS.map(x -> x + ".txt" + ":" + new Random().nextInt(10)).setParallelism(4);
DataStream<String> processed = map2Ds.keyBy(new KeySelector<String, String>() {
@Override
public String getKey(String value) throws Exception {
return value + "xxx";
}
}).process(new ProcessFunction<String, String>() {
@Override
public void processElement(String value, ProcessFunction<String, String>.Context ctx, Collector<String> out)
throws Exception {
out.collect(value.split(":")[0]);
}
}).setParallelism(4);
DataStream<String> filteDS = processed.filter(x -> x.length() % 2 == 0).setParallelism(4);
filteDS.print().setParallelism(2);
env.execute();
}
}
---
下图符合上上面的并发,以及会自动选择partition 规则,可以看到常用的规则是rebalance;
后面可以修改规则
手动修改,partition 规则
public class _02_Partition2Stream {
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
configuration.setInteger("rest.port", 8822);
// 获取环境
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(configuration);
DataStreamSource<String> dataStreamSource = env.socketTextStream("192.168.141.141", 9000);
DataStream<String> map1Ds = dataStreamSource.map(x -> "demo" + x).setParallelism(4); //修改
DataStream<String> flatMapDS = map1Ds.flatMap(new FlatMapFunction<String, String>() {
@Override
public void flatMap(String value, Collector<String> out) throws Exception {
String[] split = value.split(",");
for (String s : split) {
out.collect(s);
}
}
}).setParallelism(4); //修改
DataStream<String> map2Ds = flatMapDS.map(x -> x + ".txt" + ":" + new Random().nextInt(10)).setParallelism(4);
DataStream<String> processed = map2Ds.keyBy(new KeySelector<String, String>() {
@Override
public String getKey(String value) throws Exception {
return value + "xxx";
}
}).process(new ProcessFunction<String, String>() {
@Override
public void processElement(String value, ProcessFunction<String, String>.Context ctx, Collector<String> out)
throws Exception {
out.collect(value.split(":")[0]);
}
}).setParallelism(4);
DataStream<String> filteDS = processed.filter(x -> x.length() % 2 == 0).setParallelism(4).shuffle(); //修改
filteDS.print().setParallelism(2);
env.execute();
}
}
==
修改后数据传输如下