前言
本篇主要的目的是阐述 SparkContext 初始化过程中,重要环节源码的逻辑流程的梳理。SparkContext 实例化的过程是一个比较复杂的过程,主要包括SparkEnv、spark-history、心跳检测、状态追踪、广播、资源与任务调度、底层通信等等组件的初始化。本篇文章的主要目的在于:了解 Spark 提交任务底层是怎样将 Task 任务提交至 Executor 中;了解 SparkContext 初始化过程中几个重要的环节。
Spark 任务提交大致流程
流程说明

- 经过 SparkSubmit 的提交之后就会准备运行环境(包括:资源、SparkContext初始化等等),然后开始提交 Spark Job 任务运行;
- 当遇到动作算子(如:reduce、collect等等)的时候就会开始提交 Job,这一类算的底层一定会执行
runJob()方法并提交到 DAGschedule中; - 而后通过 DAGSchedulerEventProcessLoop(循环事务处理器)处理提交 Job,然后开始递归分段提交任务至 TaskSchedule(实际为继承的子类 TaskSchedulerImpl);
- 实际上到了这一步 Task 任务已经切分好了,并且准备提交给 Executor 执行;但是怎么直接将 Task 提交给Executor运行;,这里就需要 ScheduleBackend (后端调度器)将 Task 分发给各个 Executor;
- ScheduleBackend (实际为继承的子类 CoarseGrainedSchedulerBackend)开始通过 launchTasks 提交 Task任务至 Executor 中;
- ExecutorBackend (实际为继承的子类 CoarseGrainedExecutorBackend)接收到 Task 任务时,就会提交给 Executor 开始运行;
- Executor 接收到 Task任务时候就会开始执行;
涉及源码说明
执行Job
org.apache.spark.rdd.RDD
// 这里是以 reduce 算子为例进行说明
def reduce(f: (T, T) => T): T = withScope {
val cleanF = sc.clean(f)
................. 中间省略部分代码 ....................
// 通过SparkContext 开始提交Job
sc.runJob(this, reducePartition, mergeResult)
// Get the final result out of our Option, or throw an exception if the RDD was empty
jobResult.getOrElse(throw new UnsupportedOperationException("empty collection"))
}
org.apache.spark.SparkContext
def runJob[T, U: ClassTag](
rdd: RDD[T],
processPartition: Iterator[T] => U,
resultHandler: (Int, U) => Unit)
{
val processFunc = (context: TaskContext, iter: Iterator[T]) => processPartition(iter)
runJob[T, U](rdd, processFunc, 0 until rdd.partitions.length, resultHandler)
}
org.apache.spark.SparkContext
def runJob[T, U: ClassTag](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
resultHandler: (Int, U) => Unit): Unit = {
................. 中间省略部分代码 ....................
// 将任务提交至 DAGscheduler 进行Job 任务的划分
dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get)
progressBar.foreach(_.finishAll())
rdd.doCheckpoint()
}
提交 Stage
org.apache.spark.scheduler.DAGScheduler
def runJob[T, U](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
callSite: CallSite,
resultHandler: (Int, U) => Unit,
properties: Properties): Unit = {
val start = System.nanoTime
// 提交 Job
val waiter = submitJob(rdd, func, partitions, callSite, resultHandler, properties)
................. 中间省略部分代码 ....................
}
}
org.apache.spark.scheduler.DAGScheduler
def submitJob[T, U](
rdd: RDD[T],
func: (TaskContext, Iterator[T]) => U,
partitions: Seq[Int],
callSite: CallSite,
resultHandler: (Int, U) => Unit,
properties: Properties): JobWaiter[U] = {
................. 中间省略部分代码 ....................
assert(partitions.size > 0)
val func2 = func.asInstanceOf[(TaskContext, Iterator[_]) => _]
val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
// 通过循环事务处理提交 Job
eventProcessLoop.post(JobSubmitted(
jobId, rdd, func2, partitions.toArray, callSite, waiter,
SerializationUtils.clone(properties)))
waiter
}
org.apache.spark.scheduler.DAGSchedulerEventProcessLoop
/**
* @Author: Small_Ran
* @Date: 2022/6/1
* @Description: 根据 driver 发送过来的 事件类型,来决定到底做什么
*/
private def doOnReceive(event: DAGSchedulerEvent): Unit = event match {
// Job提交事务类型
case JobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) =>
dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, call

本文详细剖析了Spark任务提交流程,从SparkContext初始化到任务执行的全过程,包括关键组件的创建与交互,以及任务如何最终到达Executor执行。
最低0.47元/天 解锁文章
1660

被折叠的 条评论
为什么被折叠?



