接《Flink源码系列——获取JobGraph的过程》,在获取到JobGraph后,客户端会封装一个SubmitJob消息,并将其提交给JobManager,本文就接着分析,JobManager在收到SubmitJob消息后,对其处理逻辑。JobManager是一个Actor,其对接受到的各种消息的处理入口是handleMessage这个方法,其中对SubmitJob的处理入口如下:
override def handleMessage: Receive = {
…
case SubmitJob(jobGraph, listeningBehaviour) =>
val client = sender()
val jobInfo = new JobInfo(client, listeningBehaviour, System.currentTimeMillis(), jobGraph.getSessionTimeout)
submitJob(jobGraph, jobInfo)
…
}
这里构造了一个JobInfo实例,其是用来保存job的相关信息的,如提交job的客户端、客户端监听模式、任务提交的开始时间、会话超时时间、以及结束时间、耗时等信息。
其中监听模式有三种,三种模型下关心的消息内容依次增加,解释如下:
a、DETACHED —— 只关心job提交的确认消息
b、EXECUTION_RESULT —— 还关心job的执行结果
c、EXECUTION_RESULT_AND_STATE_CHANGES —— 还关心job的状态变化
然后就进入了真正的处理逻辑subminJob()方法中了,这个方法的代码稍微有点长,这里就分段进行分析,另外submitJob这个方法除了上述的jobGraph和jobInfo两个入参外,还有一个isRecovery的布尔变量,默认值是false,用来标识当前处理的是否是一个job的恢复操作。这个逻辑根据jobGraph是否为null分为两个大的分支,先看下jobGraph为null的情况,处理逻辑就是构造一个job提交异常的消息,然后通知客户端,告诉客户端jobGraph不能为null。
jobInfo.notifyClients(
decorateMessage(JobResultFailure(
new SerializedThrowable(
new JobSubmissionException(null, "JobGraph must not be null.")))))
重点还是分析jobGraph不为null的情况下的处理逻辑,这部分的逻辑也可以分为两大部分。
a、根据jobGraph构建ExecutionGraph
b、对构建好的ExecutionGraph进行调度执行
在构建ExecutionGraph这部分,会进行一些初始化的工作,如果在这过程中,发生异常,会将初始化过程做的操作进行回滚操作。
1、构建ExecutionGraph
1.1、总览
在开始ExecutionGraph的构建之前,会先获取构建所需的参数,如下:
/** 将job所需jar相关信息注册到library管理器中,如果注册失败,则抛出异常 */
try {
libraryCacheManager.registerJob(
jobGraph.getJobID, jobGraph.getUserJarBlobKeys, jobGraph.getClasspaths)
}
catch {
case t: Throwable =>
throw new JobSubmissionException(jobId,
"Cannot set up the user code libraries: " + t.getMessage, t)
}
/** 获取用户类加载器,如果获取的类加载器为null,则抛出异常 */
val userCodeLoader = libraryCacheManager.getClassLoader(jobGraph.getJobID)
if (userCodeLoader == null) {
throw new JobSubmissionException(jobId,
"The user code class loader could not be initialized.")
}
/** 判断{
@code JobGraph}中的{
@code StreamNode}的个数, 如果为0, 则说明是个空任务,抛出异常 */
if (jobGraph.getNumberOfVertices == 0) {
throw new JobSubmissionException(jobId, "The given job is empty")
}
/** 优先采用JobGraph配置的重启策略,如果没有配置,则采用JobManager中配置的重启策略 */
val restartStrategy =
Option(jobGraph.getSerializedExecutionConfig()
.deserializeValue(userCodeLoader)
.getRestartStrategy())
.map(RestartStrategyFactory.createRestartStrategy)
.filter(p => p != null) match {
case Some(strategy) => strategy
case None => restartStrategyFactory.createRestartStrategy()
}
log.info(s"Using restart strategy $restartStrategy for $jobId.")
val jobMetrics = jobManagerMetricGroup.addJob(jobGraph)
/** 获取注册在调度器上的所有TaskManager实例的总的slot数量 */
val numSlots = scheduler.getTotalNumberOfSlots()
/** 针对jobID,看是否已经存在 ExecutionGraph,如果有,则直接获取已有的,并将registerNewGraph标识为false */
val registerNewGraph = currentJobs.get(jobGraph.getJobID) match {
case Some((graph, currentJobInfo)) =>
executionGraph = graph
currentJobInfo.setLastActive()
false
case None =>
true
}
上面这段逻辑主要做一些准备工作,如jar包注册,类加载器,重启策略等,这些准备好之后,就可以开始ExecutionGraph的构建,调用如下:
/** 通过{
@link JobGraph}构建出{
@link ExecutionGraph} */
executionGraph = ExecutionGraphBuilder.buildGraph(
executionGraph,
jobGraph,
flinkConfiguration,
futureExecutor,
ioExecutor,
scheduler,
userCodeLoader,
checkpointRecoveryFactory,
Time.of(timeout.length, timeout.unit),
restartStrategy,
jobMetrics,
numSlots,
blobServer,
log.logger)
/** 如果还没有注册过, 则进行注册 */
if (registerNewGraph) {
currentJobs.put(jobGraph.getJobID, (executionGraph, jobInfo))
}
/** 注册job状态变化监听器 */
executionGraph.registerJobStatusListener(
new StatusListenerMessenger(self, leaderSessionID.orNull))
jobInfo.clients foreach {
/** 如果客户端关心执行结果和状态变化,则为客户端在executiongraph中注册相应的监听器 */
case (client, ListeningBehaviour.EXECUTION_RESULT_AND_STATE_CHANGES) =>
val listener = new StatusListenerMessenger(client, leaderSessionID.orNull)
executionGraph.registerExecutionListener(listener)
executionGraph.registerJobStatusListener(listener)
case _ => // 如果不关心,则什么都不做
}
在ExecutionGraph构建好只有,就会设置相应的监听器,用来监听其后续的调度执行情况。
另外这段代码的执行会被整个的进行了try…catch,看下catch中的逻辑。
/** 如果异常, 则进行回收操作 */
case t: Throwable =>
log.error(s"Failed to submit job $jobId ($jobName)", t)
/** 进行jar包的注册回滚 */
libraryCacheManager.unregisterJob(jobId)
blobServer.cleanupJob(jobId)
/** 移除上面注册的graph */
currentJobs.remove(jobId)
/** 如果executionGraph不为null,还需要执行failGlobal操作 */
if (executionGraph != null) {
executionGraph.failGlobal(t)
}
/** 构建JobExecutionException移除 */
val rt: Throwable = if (t.isInstanceOf[JobExecutionException]) {
t
} else {
new JobExecutionException(jobId, s"Failed to submit job $jobId ($jobName)", t)
}
/** 通知