这里包含Graph的处理,最终生成物理执行图,以及Slot到发布Task到TaskExecutor上运行。如下所示:
- Flink编程套路总结
- Flink提交执行脚本分析
- Flink CliFronted提交应用程序源码剖析
- ExecutionEnvironment源码解析
- Job提交流程源码分析
- StreamGraph构建和提交源码解析
- JobGragh构建和提交源码解析
- ExecutionGragh构建和提交源码解析
- Slot管理(申请)源码解析
- Task提交到TaskExecutor执行源码剖析
1、Flink Program编程套路总结
Flink底层提供了一个功能完善且复杂的分布式流式计算引擎,但是上层的应用API却很简单,简单来说,把整个Flink应用程序的编写,抽象成三个方面:
- 执行环境 ExectionEnvironment
- 数据抽象 DataSet DataStream
- 逻辑操作 Source Transformation Sink
所以Flink的应用程序在编写的时候,基本是一个简单的统一套路:
1、获取执行环境对象
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
2、通过执行环境对象,注册数据源Source,得到数据抽象
DataStream ds = env.socketTextStream(...)
3、调用数据抽象的各种Transformation执行逻辑计算
DataStream resultDS = ds.flatMap(...).keyBy(...).sum(...);
4、将各种Transformation执行完毕之后得到的计算结果数据抽象注册 Sink
resultDS.addSink(...)
5、提交Job执行
env.execute(...)
在Flink应用程序中,其实所有的操作,都是StreamOperator,分为SourceOperator,SinkOperator,StreamOperator,然后能被优化的Operator就会chain在一起,形成一个OperatorChain。
基本路数,和Spark一致,并且,在Flink-1.12版本中,DataStream已经具备高效批处理操作处理了。更加做到了流批处理的统一(API统一)。据"Flink Forward Asia 2020 在线峰会"阿里流式计算负责人王峰介绍:在Flink-1.13版本,将会完全统一流批处理的API。
2、Flink Job提交脚本解析
当编写好Flink的应用程序,正常的提交方式为:打成jar包,通过flink命令来进行提交。
flink run xxx.jar class arg1 arg2
flink命令脚本的底层,是通过java命令启动:CliFrontend类来启动JVM进程执行任务的构造和提交:
# 注释 去提交一个jar到Flink集群运行
# 会到CliFrontend的main方法提交任务
# Add HADOOP_CLASSPATH to allow the usage of Hadoop file systems
exec $JAVA_RUN $JVM_ARGS $FLINK_ENV_JAVA_OPTS "${log_setting[@]}" -classpath "`manglePathList "$CC_CLASSPATH:$INTERNAL_HADOOP_CLASSPATHS"`" org.apache.flink.client.cli.CliFrontend "$@"
3、Flink CliFronted提交应用程序源码剖析
找到CliFrontend的main方法,来执行任务的提交,核心代码如下:
/*****************************************
* TODO
* 注释: 初始化 CliFrontend
*/
final CliFrontend cli = new CliFrontend(configuration, customCommandLines);
/*************************************************
* TODO
* 注释: 运行
* 解析命令行并并开始请求操作
*
* flink run class arg1
* args[0] = run
*/
int retCode = SecurityUtils.getInstalledContext().runSecured(() -> cli.parseParameters(args));
System.exit(retCode);
进入到parseParameters(String[] args)方法,由于第一个参数为run,会进入到run(String[] args)方法。会做以下事情,包括解析jar包,找到主类以及依赖,然后通过反射的方式提交任务。
/*************************************************
* TODO 注释: 构建 Program 打包好了的程序,要去解析jar包,找到主类以及依赖。
*
*/
final PackagedProgram program = getPackagedProgram(programOptions);
// TODO 注释: 依赖jar处理
final List<URL> jobJars = program.getJobJarAndDependencies();
/*************************************************
* TODO
* 注释: 有效配置
* 1、activeCommandLine
* 2、commandLine
* 3、programOptions 程序参数
* 4、jobJars 依赖jar
*/
final Configuration effectiveConfiguration = getEffectiveConfiguration(activeCommandLine, commandLine, programOptions, jobJars);
/*************************************************
* TODO
* 注释:把任务提交执行
*/
executeProgram(effectiveConfiguration, program);
最终是在PackagedProgram.callMainMethod(...)方法中通过mainMethod.invoke(null, (Object) args);来跳转到运行主类的 main 方法。
总结如下:
当用户把Flink应用程序打成jar使用flink run ... 的shell命令提交的时候,底层是通过CliFrontend来处理。底层的逻辑,就是通过反射来调用用户程序的 main() 方法执行。
在刚组建内部,主要有以下几件事要做:
1、根据 flink 后面的执行命令来确定执行方法(run ==> run(params))
2、解析 main 参数,构建 PackagedProgram,然后执行 PackagedProgram
3、通过反射获取应用程序的 main 方法的实例,通过反射调用执行起来
总的来说,就是准备执行Program所需要的配置,jar包,运行主类等的必要的信息,然后提交执行。
4、ExecutionEnvironment源码解析
Flink应用程序的执行,首先就是创建运行环境StreamExecutionEnvironment,一般在企业环境中,都是通过getExecutionEnvironment()来获取ExecutionEnvironment,如果是本地运行的话,则会获取到:LocalStreamEnvironment,如果是提交到Flink集群运行,则获取到:StreamExecutionEnvironment。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
StreamExecutionEnvironment是Flink应用程序的执行入口,提供了一些重要的操作机制:
- 提供了readTextFile(),socketTextStream(),createInput(),addSource()等方法去对接数据源
- 提供了setParallelism()设置程序的并行度
- StreamExecutionEnvironment管理了ExecutionConfig对象,该对象负责Job执行的一些行为配置管理。还管理了Configuration管理一些其他的配置
- StreamExecutionEnvironment管理了一个List<Transformation<?>> transformations成员变量,该成员变量,主要用于保存Job的各种算子转化得到的Transformation,把这些Transformation按照逻辑拼接起来,就能得到StreamGragh(Transformation->StreamOperator->StreamNode)
- StreamExecutionEnvironment提供了execute()方法主要用于提交Job执行。该方法接收的参数就是:StreamGraph
StreamExecutionEnvironment是Flink应用程序执行的上下文,提供了很多功能,不过重点关注以上五点即可。
5、算子转换流程
这里以SocketWindowWordCount.java为例,查看其中的flatMap算子。
// TODO 注释: 将算子生成 Transformation 加入到 Env 中的 transformations 集合中
DataStream<WordWithCount> windowCounts = text.flatMap(...)
// StreamFlatMap 是一个 Function 也是一个 StreamOperator
// flatMapper 是一个function
// 最终调用 transform 方法来把StreamFlatMap这种StreamOperator转换成Transformation,最终加入到StreamExectiionEnvironment的List<Transformation<?>> transformations
transform("Flat Map", outputType, new StreamFlatMap<>(clean(flatMapper)));
// 把Operator注册到执行环境中,用于生成StreamGraph。将该transformation注册到执行环境中,当执行generate方法时,生成StreamGraph图结构。
getExecutionEnvironment().addOperator(resultTransform);
// 将 Transformation 加入transformations集合
StreamExecutionEnvironment.transformations.add(transformation);
最终是变成transformation,加入到List<Transformation<?>>
DataStream的主要分类:
- DataStreamSource 流数据源
- DataStreamSink 流数据目的地
- KeyedStream 按key分组的数据流
- DataStream 普通数据流
关于函数的理解:
- Function:传参
- Operator:Graph中抽象概念
- Transformation:一种针对流的逻辑操作
总结:Function ---> Operator ---> Transformation
6、Job提交流程源码分析
1、核心流程如下:
// 核心入口
env.execute("Streaming WordCount");
// 1、负责生成 StreamGraph
// 2、负责执行 StreamGraph
execute(getStreamGraph(jobName));
第一步:getStreamGraph(jobName) 生成StreamGraph解析
//调用generate()方法生成StreamGragh
StreamGraph streamGraph = getStreamGraphGenerator().setJobName(jobName).generate();
//构建一个StreamGraph
streamGraph = new StreamGraph(...);
//TODO 执行各种算子的transformation:由算子生成Transformation来构建StreamGraph当时在执行各种算子的时候,就已经把算子转换成对应的Transformation放入transformations集合中了自底向上(先遍历inputtransformations)对转换树的每个transformation进行转换
for(Transformation<?> transformation : transformations){
transform(transformation);
}
transform(transformation) 从Env对象中,把Transformation拿出来,然后转换成StreamNode;
Function-->Operator-->Transformation-->StreamNode;
transform(transformation);
// 添加一个Operator(StreamGraph端会添加一个StreamNode)
streamGraph.addOperator(...);
// 设置并行度
streamGraph.setParallelism(transform.getId(), parallelism);
// 设置该 StreamNode 的入边 SreamEdge
for(Integer inputId : inputIds) {
streamGraph.addEdge(inputId, transform.getId(), 0);
// 内部实现
// TODO 注释: 找到 该 StreamNode 的上游顶点
StreamNode upstreamNode = getStreamNode(upStreamVertexID);
// TODO 注释: 找到 该 StreamNode 的下游顶点
StreamNode downstreamNode = getStreamNode(downStreamVertexID);
// 构建 StreamNode 之间的 边(StreamEdge) 对象
StreamEdge edge = new StreamEdge(upstreamNode, downstreamNode, ...)
// TODO 注释: 给 上游 StreamNode 设置 出边
getStreamNode(edge.getSourceId()).addOutEdge(edge);
// TODO 注释: 给 下游 StreamNode 设置 入边
getStreamNode(edge.getTargetId()).addInEdge(edge);
}
第二步:execute(StreamGraph) 解析
/*************************************************
* TODO
* 注释: 异步提交执行 StreamGraph
* 跳转到: AbstractSessionClusterExecutor 的 execute() 方法
*/
CompletableFuture<JobClient> jobClientFuture = executorFactory.getExecutor(configuration).execute(streamGraph, configuration);
AbstractSessionClusterExecutor.execute(...)
// 获取JobGraph
final JobGraph jobGraph = PipelineExecutorUtils.getJobGraph(pipeline, configuration);
// 提交执行:1、MiniClusterClient 本地执行;2、RestClusterClient提交到Flink Rest服务接收处理
clusterClient.submitJob(jobGraph)...
// 正式提交任务
requestFuture.thenCompose(...)
// 提交 通过 Http Restful 方式提交, 提交Request 给 WebMonitorEndpoint, 最终由 JobSubmitHandler 来执行请求处理
restClient.sendRequest()
// 提交请求
submitRequest(targetAddress, targetPort, httpRequest, responseType);
// 发送请求到WebMonitorEndpoint的Netty服务端,最终由JobSubmitHandler来执行处理
httpRequest.writeTo(channel);
-
PipelineExecutorUtils.getJobGraph(pipeline, configuration):将StreamGraph转换成JobGraph;
-
RestClusterClient.submitJob(...):将jobGraph持久化到磁盘文件形成jobGraphFile
-
持久化 JobGragh 的前缀:flink-jobgraph
-
持久化 JobGragh 的后缀:.bin
-
当我们把 JobGraph 持久化了之后,变成了一个文件: jobGraphFile。然后其实,在提交 JobGraph 到 Flink 集群运行的时候,其实提交的就是: 这个文件!将来,最终是由 flink 集群的 WebMonitor(JobSubmitHandler) 去接收请求来执行处理。JobSubmitHandler 在执行处理的第一件事情: 把传送过来的这个文件反序列化得到 JobGraph 这个对象。
-
-
restClient.sendRequest(...) :任务的正式提交
-
使用netty客户端提交任务,最终通过channel把请求数据,发送给WebMonitorEndpoint中的JobSubmitHandler来执行处理。
-
7、Flink Graph演变
Flink的一个Job。最终,归根结底,还是构建一个高效率的能用于分布式并行执行的DAG执行图。
1、帮我们把上下游两个相邻算子如果能chain到一起,则chain到一起做优化
2、chain到一起的多个Operator就会组成一个OperatorChain,当OperatorChain执行的时候,到底要执行多少个Task,则就需要把DAG进行并行化变成实实在在的Task来调度执行
一个Flink流式作业,从Client提交到Flink集群,到最后执行,总共会经历始终能够不同的状态。总的来说:
1、Client首先根据用户编写的代码生成StreamGraph, 然后StreamGraph构建成JobGraph提交给Flink集群主节点
2、然后启动到JobMaster在接收到JobGraph后,会对其进行并行化生成ExecutionGraph后调度启动StreamTask执行。
3、StreamTask并行化的运行在Flink集群中的,就是最终的物理执行图状态结构。
Flink中的执行图可以分为四层:StreamGraph==>JobGraph==>ExecutionGraph==>物理执行图
- StreamGraph:是根据用户通过StreamAPI编写的代码生成的最初的图。用来表示程序的拓扑结构。
- StreamNode:用来代表 operator 的类,并具有所有相关的属性,如并发度、入边和出边等。
- StreamEdge:表示连接两个StreamNode的边。
- JobGraph:StreamGraph经过优化后生成了JobGraph,提交给JobManager的数据结构。主要的优化为,将多个符合条件的节点chain在一起作为一个节点,这样可以减少数据在节点之间流动所需要的序列化反序列化传输消耗。(如下图Operator Chain)
- JobVertex:经过优化后符合条件的多个StreamNode可能会chain在一起生成一个JobVertex,即一个JobVertex包含一个或多个operator,JobVertex的输入是JobEdge,输出是IntermediateDataSet。
- IntermediateDataSet:表示JobVertex的输出,即经过operator处理产生的数据集。producer是JobVertex,consumer是JobEdge。
- JobEdge:代表了job graph中的一条数据传输通道。source 是 IntermediateDataSet,target 是 JobVertex。即数据通过JobEdge由IntermediateDataSet传递给目标JobVertex。
- ExecutionGraph:JobManager根据JobGraph生成ExecutionGraph。ExecutionGraph是JobGraph的并行化版本,是调度层最核心的数据结构。
-
ExecutionJobVertex:和JobGraph中的JobVertex一一对应。每一个ExecutionJobVertex都有和并发度一样多的 ExecutionVertex。
-
ExecutionVertex:表示ExecutionJobVertex的其中一个并发子任务,输入是ExecutionEdge,输出是IntermediateResultPartition。
-
IntermediateResult:和JobGraph中的IntermediateDataSet一一对应。一个IntermediateResult包含多个IntermediateResultPartition,其个数等于该operator的并发度。
-
IntermediateResultPartition:表示ExecutionVertex的一个输出分区,producer是ExecutionVertex,consumer是若干个ExecutionEdge。
-
ExecutionEdge:表示ExecutionVertex的输入,source是IntermediateResultPartition,target是 ExecutionVertex。source和target都只能是一个。
-
Execution:是执行一个 ExecutionVertex 的一次尝试。当发生故障或者数据需要重算的情况下 ExecutionVertex 可能会有多个 ExecutionAttemptID。一个 Execution 通过 ExecutionAttemptID 来唯一标识。JM和TM之间关于 task 的部署和 task status 的更新都是通过 ExecutionAttemptID 来确定消息接受者。
-
- 物理执行图:JobManager根据ExecutionGraph对Job进行调度后,在各个TaskManager上部署Task后形成的图,并不是一个具体的数据结构。
- Task:Execution被调度后在分配的 TaskManager 中启动对应的 Task。Task 包裹了具有用户执行逻辑的 operator。
- ResultPartition:代表由一个Task的生成的数据,和ExecutionGraph中的IntermediateResultPartition一一对应。
- ResultSubpartition:是ResultPartition的一个子分区。每个ResultPartition包含多个ResultSubpartition,其数目要由下游消费 Task 数和 DistributionPattern 来决定。
- InputGate:代表Task的输入封装,和JobGraph中JobEdge一一对应。每个InputGate消费了一个或多个的ResultPartition。
- InputChannel:每个InputGate会包含一个以上的InputChannel,和ExecutionGraph中的ExecutionEdge一一对应,也和ResultSubpartition一对一地相连,即一个InputChannel接收一个ResultSubpartition的输出。
7.1、StreamGraph转换为JobGraph
具体转换代码如下,最主要的是下面的setChaining(...)方法,
- 一个 StreamNode 也可以认为是 做了 chain 动作 StreamNode -> JobVertex
- 两个 StreamNode 做了 chain 动作 StreamNode + StreamNode -> JobVertex
final JobGraph jobGraph = PipelineExecutorUtils.getJobGraph(pipeline, configuration);
// 在这里完成所谓的StreamGraph到JobGraph的转换
JobGraph jobGraph = new StreamingJobGraphGenerator(streamGraph, jobID).createJobGraph();
/*************************************************
* TODO
* 注释: 重点: 设置 Chaining, 将可以 Chain 到一起的 StreamNode Chain 在一起,
* 这里会生成相应的 JobVertex 、JobEdge 、 IntermediateDataSet 对象
* 把能 chain 在一起的 Operator 都合并了,变成了 OperatorChain
* -
* 大致逻辑:
* 这里的逻辑大致可以理解为,挨个遍历节点:
* 1、如果该节点是一个 chain 的头节点,就生成一个 JobVertex,
* 2、如果不是头节点,就要把自身配置并入头节点,然后把头节点和自己的出边相连;
* 对于不能chain的节点,当作只有头节点处理即可
* -
* 作用:
* 能减少线程之间的切换,减少消息的序列化/反序列化,减少数据在缓冲区的交换,减少了延迟的同时提高整体的吞吐量。
*/
setChaining(hashes, legacyHashes);
// TODO 注释: 设置 PhysicalEdges
// TODO 注释: 将每个 JobVertex 的入边集合也序列化到该 JobVertex 的 StreamConfig 中
// TODO 注释: 出边集合,已经在 上面的代码中,已经搞定了。
setPhysicalEdges();
// TODO 注释: 设置 SlotSharingAndCoLocation
setSlotSharingAndCoLocation();
// TODO 注释: 设置 ManagedMemoryFraction
setManagedMemoryFraction(
Collections.unmodifiableMap(jobVertices),
Collections.unmodifiableMap(vertexConfigs),
Collections.unmodifiableMap(chainedConfigs),
id -> streamGraph.getStreamNode(id).getMinResources(),
id -> streamGraph.getStreamNode(id).getManagedMemoryWeight()
);
// TODO 注释: 设置 SnapshotSettings, checkpoint 相关的设置
configureCheckpointing();
// TODO 注释: 设置 SavepointRestoreSettings
jobGraph.setSavepointRestoreSettings(streamGraph.getSavepointRestoreSettings());
// TODO 注释: 添加 UserArtifactEntries
JobGraphUtils.addUserArtifactEntries(streamGraph.getUserArtifacts(), jobGraph);
// set the ExecutionConfig last when it has been finalized
// TODO 注释: 传递执行环境配置, 设置 ExecutionConfig
jobGraph.setExecutionConfig(streamGraph.getExecutionConfig());
那么,设计中为什么要设计这么四层执行逻辑呢?它的意义是什么?
- StreamGraph是对用户逻辑的映射
- JobGraph在StreamGraph基础上进行了一些优化,比如把一部分操作串成chain以提高效率
- ExecutionGraph是为了调度存在的,加入了并行处理的概念
- 物理执行结构:真正执行的是Task及其相关结构
8、服务端处理
8.1、WebMonitorEndpoint处RestClient的JobSubmit
8.1.1、任务提交
当客户端把任务提交到flink集群时,服务端中的JobSubmitHandler.handleRequest(...)来接收处理请求。主要步骤如下:
// TODO 注释: 从请求中获取文件: 包含JobGraph序列化文件
final Collection<File> uploadedFiles = request.getUploadedFiles();
final Map<String, Path> nameToFile = uploadedFiles.stream().collect(Collectors.toMap(File::getName, Path::fromLocalFile));
// TODO 拿到请求体
final JobSubmitRequestBody requestBody = request.getRequestBody();
/*************************************************
* TODO 恢复得到 JobGragh
* 由此可见: 服务端接收到客户端提交的,其实就是一个 JobGragh
* 到此为止: 我们可以认为:客户端终于把 JobGragh 提交给 JobManager 了。 最终由 JobSubmitHandler 来执行处理
*/
CompletableFuture<JobGraph> jobGraphFuture = loadJobGraph(requestBody, nameToFile);
// TODO 得到 jar
Collection<Path> jarFiles = getJarFilesToUpload(requestBody.jarFileNames, nameToFile);
// TODO 获取 依赖 jar
Collection<Tuple2<String, Path>> artifacts = getArtifactFilesToUpload(requestBody.artifactFileNames, nameToFile);
//TODO 上传: JobGraph + 程序jar + 依赖 jar
CompletableFuture<JobGraph> finalizedJobGraphFuture = uploadJobGraphFiles(gateway, jobGraphFuture, jarFiles, artifacts, configuration);
//TODO 提交任务
CompletableFuture<Acknowledge> jobSubmissionFuture = finalizedJobGraphFuture.thenCompose(
// TODO 注释: 提交任务
// TODO 注释: gateway = Dispatcher
// TODO 注释: Flink 集群主节点: JobManager(ResourceManager, WebMonitorEndpoint, Dsipatcher)
jobGraph -> gateway.submitJob(jobGraph, timeout)
);
其中Flink集群主节点(JobManager)的三个重要组件:
- ResourceManager:申请资源
- WebMonitorEndpoint:接收请求执行处理
- Dispatcher:提交任务,如上图中的gateway.submitJob(...)
在这里,服务端进行了任务的持久化和提交,核心流程如下:
// TODO gateway = Dispatcher
gateway.submitJob(jobGraph, timeout)
internalSubmitJob(jobGraph);
// TODO 持久化和提交
waitForTerminatingJobManager(...)
// TODO 注释: 保存 JobGraph
// TODO 注释: jobGraphWriter = ZooKeeperJobGraphStore。把jobGraph的信息保存到zk里面。
jobGraphWriter.putJobGraph(jobGraph);
// 运行 job
CompletableFuture<Void> runJobFuture = runJob(jobGraph);
提交任务后,就是任务的执行。上图中的run(...)方法,那在这里是有做两件事。第一:创建JobMaster,在创建JobMaster的时候,同时会把 JobGraph变成ExecutionGraph。第二:提交任务。核心代码如下:
/*************************************************
* TODO 注释: 创建 JobManagerRunner
* 在这里面会做一件重要的事情:
* 1、创建 JobMaster 实例
* 2、在创建 JobMaster 的时候,同时会把 JobGraph 编程 ExecutionGraph
* -
* 严格来说,是启动 JobMaster, 那么这个地方的名字,就应该最好叫做: createJobMasterRunner
* Flink 集群的一两个主从架构:
* 1、资源管理: ResourceManager + TaskExecutor
* 2、任务运行: JobMaster + StreamTask
*/
final CompletableFuture<JobManagerRunner> jobManagerRunnerFuture = createJobManagerRunner(jobGraph);
// 提交任务
FunctionUtils.uncheckedFunction(this::startJobManagerRunner)
在这里,先看下如何将JobGraph转换成ExecutionGraph。
// 第一件事:主要的事情,是创建JobMaster
createJobManagerRunner(jobGraph);
jobMasterFactory.createJobMasterService(...);
// 创建DefaultScheduler
createScheduler(jobManagerJobMetricGroup);
this.executionGraph = createAndRestoreExecutionGraph(...);
// 创建 ExecutionGraph
createExecutionGraph(...);
第二步:启动JobMaster
Dispatcher.startJobManagerRunner(...);
jobManagerRunner.start();
// 注释:ZooKeeperLeaderElectionService = leaderElectionService。选举成功,则跳转到:ZooKeeperLeaderElectionService 的isLeader()方法
leaderElectionService.start(this);
JobManagerRunnerImpl.grantLeadership(...)
// 注释: 调度并启动JobManager
verifyJobSchedulingStatusAndStartJobManager(...);
startJobMaster(...)
JobMaster启动好后,会向ResourceManager注册和维持心跳。TaskExecutro也会向JobMaster注册和维持心跳。在JobMaster中的
- startJobMasterServices(); 初始化一些必要的服务组件。JobMaster 的注册和心跳
- resetAndStartScheduler(); 开始申请Slot。JobMaster调度StreamTask去运行
这里看下flink任务的资源申请:
//开始调度执行。JobMaster调度StreamTask去运行
resetAndStartScheduler();
// TODO 启动所有的服务协调组件
startAllOperatorCoordinators();
// TODO 开始调度
startSchedulingInternal();
/*************************************************
* TODO 注释: 准备 ExecutionGraph
*/
prepareExecutionGraphForNgScheduling();
/*************************************************
* TODO 注释: 调度
* 流式作业默认调度方式: schedulingStrategy = EagerSchedulingStrategy
* 1、EagerSchedulingStrategy(主要用于流式作业,所有顶点(ExecutionVertex)同时开始调度)
* 2、LazyFromSourcesSchedulingStrategy(主要用于批作业,从 Source 开始开始调度,其他顶点延迟调度)调度
*/
schedulingStrategy.startScheduling();
/*************************************************
* TODO 从ExecutionGraph中获取待调度的 ExecutionVertex
* allocateSlotsAndDeploy(...); 参数为总并行度,这样子就知晓总共需要申请多少 Slot 了
*/
allocateSlotsAndDeploy(...);
8.1.2、slot管理(申请和释放)
allocateSlots(...)可以分为以下4个步骤
- JobMaster发送请求申请slot
- ResourceManager接收到请求,执行slot请求处理
- TaskManager处理ResourceManager发送过来的slot请求
- JobMaster接收到TaskManager发送过来的slot申请处理结果
对应的组件:
1、ResourceManager管理所有的TaskManager(TaskExecutor)
2、TaskExecutor中关于资源的管理,使用slot的抽象:
slot的状态管理
专门有一个做slot管理的slotManagerImpl
3、JobMaster申请slot,管理组件:slotPool
slot共享:既然有slot共享的概念,如果要执行一个Task,其实就可以先尝试从SlotPool中申请,如果申请不倒,则再向ResourceManager申请