Spark Job提交的整体流程源码分析
本文基于spark3.0.1.
任何一个行动算子,都会执行:runJob()
val results = sc.runJob()
runJob()在DAGScheduler类中
1. 提交job,是一个阻塞线程
val waiter = submitJob(rdd, func, partitions, callSite, resultHandler, properties) //742
2. submitJob中:
val jobId = nextJobId.getAndIncrement() //生成JobId, 696
3. if (partitions.isEmpty) {
return new JobWaiter() //如果分区为空,跳出这个方法 708
}
4. eventProcessLoop.post(JobSubmitted) //向事件循环中提交事件 714
5.post方法中:
eventQueue.put(event) //将事件放入队列 105
6. 在eventQueue中会起一个线程,会不断从队列中取出事件。
private[spark] val eventThread = new Thread(name) {
override def run(): Unit = {
while (!stopped.get)
val event = eventQueue.take()
onReceive(event) //79 在EventLoop类中
}
}
7.onReceive是一个抽象方法,其实现方法中执行doOnReceive(event):
override def onReceive(event: DAGSchedulerEvent): Unit = {
doOnReceive(event) //2152
}
8. doOnReceive(event)中:
private def doOnReceive(event: DAGSchedulerEvent): Unit = event match {
//上面向eventProcessLoop中post就是JobSubmitted
case JobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties) =>
dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, callSite, listener, properties)
}
"-----------DAGScheduler开始工作--------------"
9. dagScheduler.handleJobSubmitted :正式处理我们提交的Job
handleJobSubmitted中:
var finalStage: ResultStage = null // 从后面往前推 982
finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite) //986
10. createResultStage方法中:
//先获得ParentStages,即ShuffleMapStage
val parents = getOrCreateParentStages(rdd, jobId) //454
//根据ShuffleMapStage创建ResultStage
val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite)
11. private def getOrCreateParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
//获取最后一个RDD的shuffle依赖,只获取直接父级的shuffle依赖,再往前的不获取
getShuffleDependencies(rdd).map { shuffleDep =>
getOrCreateShuffleMapStage(shuffleDep, firstJobId)
}.toList
}
12. getShuffleDependencies()中:
private[scheduler] def getShuffleDependencies(
rdd: RDD[_]): HashSet[ShuffleDependency[_, _, _]] = {
//存储的是当前RDD有shuffle依赖的的直接父RDD的shuffle依赖信息
val parents = new HashSet[ShuffleDependency[_, _, _]]
// 存储哪些已经查找过了的RDD
val visited = new HashSet[RDD[_]]
//存储哪些是待查找的RDD
val waitingForVisit = new ListBuffer[RDD[_]]
// 将待查找的RDD加入集合
waitingForVisit += rdd
//如果集合非空,刚放进去,肯定不是空
while (waitingForVisit.nonEmpty) {
//获取要查找的RDD
val toVisit = waitingForVisit.remove(0)
// 如果还没有查找过
if (!visited(toVisit)) {
//就先添加到已经访问过的RDD的Set中,下一步再对它获取依赖关系
visited += toVisit
// 获取依赖关系
toVisit.dependencies.foreach {
//如果是shuffle依赖(宽依赖)
case shuffleDep: ShuffleDependency[_, _, _] =>
//加入shuffle依赖的集合
parents += shuffleDep
//如果是窄依赖
case dependency =>
//将其父RDD添加到待查找的集合前面,按此一直往前找,直到找到shuffle依赖
waitingForVisit.prepend(dependency.rdd)
}
}
}
//返回当前节点的shuffle依赖
parents
}
13. 回到第 11 步:
getShuffleDependencies(rdd).map { shuffleDep =>
//一旦查找到父RDD的shuffle依赖信息,此时就创建一个ShuffleMapStage
getOrCreateShuffleMapStage(shuffleDep, firstJobId)
}.toList
"---------------DAGScheduler开始根据shuffle划分stage--------------"
14. getOrCreateShuffleMapStage()中:
shuffleIdToMapStage.get(shuffleDep.shuffleId) match {
case Some(stage) =>
stage
case None =>
// 为当前shuffle依赖的所有祖先shuffle依赖创建ShuffleMapStage,注意,是所有
getMissingAncestorShuffleDependencies(shuffleDep.rdd).foreach { dep =>
if (!shuffleIdToMapStage.contains(dep.shuffleId)) {
createShuffleMapStage(dep, firstJobId)
createShuffleMapStage方法中:
//继续调用getOrCreateParentStages,其实就是利用递归一层一层往上找
val parents = getOrCreateParentStages(rdd, jobId) //391
val stage = new ShuffleMapStage()
}
}
// 最后,再创建当前shuffle依赖关系的ShuffleMapStage
createShuffleMapStage(shuffleDep, firstJobId) //358
//至此,所有的ShuffleMapStage创建完成,还有resultStage没创建
}
15. 然后,看第 10 步,根据ShuffleMapStage创建ResultStage, 然后赋值给第 9 步的finalStage
val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite) //456
16. 获得finalStage后,接第 9 步,往下
//如果是ResultStage,提交result-job,如果是ShuffleMapStage,运行 map-shuffle job
val job = new ActiveJob(jobId, finalStage, callSite, listener, properties) //1022
17. finalStage.setActiveJob(job) //1033
18. submitStage(finalStage) //1038 提交最后的阶段,DAGScheduler完成工作
19. submitStage方法中:
/* Submits stage, but first recursively submits any missing parents. */
/* 翻译:首先会递归提交所有缺失的ShuffleMapStage*/
private def submitStage(stage: Stage): Unit =
//判断是否有正在等待提交的,正在运行的,提交失败的ShuffleMapStage
if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage))
//如果有将这些ShuffleMapStage按照ID升序排序,ID小的会被优先提交
val missing = getMissingParentStages(stage).sortBy(_.id)
//如果没有这些ShuffleMapStage
if (missing.isEmpty)
//提交当前就是封装Task来运行这个阶段的逻辑
submitMissingTasks(stage, jobId.get)
submitMissingTasks方法中:
val tasks: Seq[Task[_]] = try //1217
stage match {
case stage: ShuffleMapStage =>
partitionsToCompute.map { id =>
//如果是ShuffleMapStage,每个分区都创建一个 ShuffleMapTask
new ShuffleMapTask())
}
case stage: ResultStage =>
partitionsToCompute.map { id =>
//如果是ShuffleMapStage,每个分区都创建一个 ResultTask
new ResultTask()
}
}
if (tasks.nonEmpty) { //1249
//taskScheduler将Task以TaskSet的形式提交
taskScheduler.submitTasks(new TaskSet, ...) ---此处转到taskScheduler干活
)
}
} else {
//如果有这些ShuffleMapStage,就一个个的递归提交
for (parent <- missing) {
submitStage(parent)
}
}
"---------------taskScheduler开始干活---------------"
1. taskScheduler.submitTasks(new TaskSet, ...)):
submitTasks是一个抽象方法,其实现方法:
val tasks = taskSet.tasks //将task从taskset中取出来
//创建一个TaskSetManager,每个taskset都会创建一个taskmanager
val manager = createTaskSetManager(taskSet, maxTaskFailures) //219
//将创建的 TaskSetManager加入到 schedulableBuilder(任务调度池)中
schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)
'SchedulableBuilder介绍:是一个特质,其有两个实现类:FIFOSchedulableBuilder和FairSchedulableBuilder,有三个方法,分别是根调度池和子调度池,和addTaskSetManager。根调度池下有Schedulablequeue,其中便放置TaskSetManager。SparkContext初始化时,默认使用FIFOSchedulableBuilder。
//从调度池中,取出TaskSetManager,开始调度
backend.reviveOffers() // backend是一个SchedulerBackend对象,负责系统的调度通讯
2. reviveOffers() 中:
reviveOffers() 是一个SchedulerBackend的抽象方法,其实现方法在CoarseGrainedSchedulerBackend中:
override def reviveOffers(): Unit = {
driverEndpoint.send(ReviveOffers) //向driverEndpoint发送ReviveOffers消息
}
3. send方法需要在receive方法中查看,DriverEndpoint的receive方法中:
override def receive: PartialFunction[Any, Unit] = {
case ReviveOffers =>
makeOffers() //169
...
}
4. makeOffers()中:
//筛选当前active状态的所有的executors
val activeExecutors = executorDataMap.filterKeys(isExecutorActive) //291
val workOffers = activeExecutors.map{
case (id, executorData) => new WorkerOffer // 每个active的executor都创建一个 WorkerOffer
}
//此方法由集群调用,在集群的slave准备资源。根据TaskSet的优先级,以轮询的方式发送到任务,以保证负载均衡
val taskDescs = scheduler.resourceOffers(workOffers) //300
5. resourceOffers()中:
//获得一个排序后的TaskSet集合
val sortedTaskSets = rootPool.getSortedTaskSetQueue.filterNot(_.isZombie) // 436
6. getSortedTaskSetQueue()中:
val sortedTaskSetQueue = new ArrayBuffer[TaskSetManager]
//从rootPool中获取schedulableQueue,之后将队列中的TaskSet,调用调度算法(FIFO)进行排序
val sortedSchedulableQueue =
schedulableQueue.asScala.toSeq.sortWith(taskSetSchedulingAlgorithm.comparator)
taskSetSchedulingAlgorithm默认是FIFOSchedulingAlgorithm(),是通过stageId进行比较的
这个方法得到一个按stageId从小到大排序后的TaskSet集合
7. 得到排序后的taskset集合后,再获取集合中每一个taskset'最高的本地化级别
for (taskSet <- sortedTaskSets) {
for (currentMaxLocality <- taskSet.myLocalityLevels) //462
...
}
'关于本地化级别:myLocalityLevels是TaskSetManager的一个属性,本地化级别有五种:
PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY
Spark调度总是会尽量让每个task以最高的本地性级别来启动,即PROCESS_LOCAL(进程本地化),是让task和数据都 同在一个executor中,这种情况性能最佳。
8. return tasks //返回各种处理后的tasks,第 4 步的resourceOffers()就走完了。 557
"------------------下面是将task发往executor------------------"
9. 接第 4 步:
if (taskDescs.nonEmpty) {
launchTasks(taskDescs)
}
launchTasks():
//将task的描述信息序列化
val serializedTask = TaskDescription.encode(task) //341
//获取当前Tasks应该发往的executor的信息
val executorData = executorDataMap(task.executorId)
//向executorEndpoint发送LaunchTask信息,同时将 TaskDesc也发送过去
executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
需要在CoarseGrainedExecutorBackend(这个就是executorEndpoint)的receive方法中查看消息:
override def receive: PartialFunction[Any, Unit] =
case LaunchTask(data) =>
val taskDesc = TaskDescription.decode(data.value) //将task描述信息反序列化
executor.launchTask(this, taskDesc) //166
launchTask()中:
val tr = new TaskRunner(context, taskDescription) //创建一个运行task的线程
TaskRunner线程的run方法中:
//从之前的task描述信息的中获取真正的task
task = ser.deserialize[Task[Any]](
taskDescription.serializedTask, Thread.currentThread.getContextClassLoader) // 406
//获取task后,run!
val res = task.run():
runTask(context) //127
不同类型的Task运行自己的处理逻辑, 有两种,ShuffleMapTask和ResultTask
'ShuffleMapTask:
这个task逻辑是将当前stage的结果写出,然后供下一个stage使用,所以一定会有write方法
ShuffleMapTask的runTask():
dep.shuffleWriterProcessor.write(rdd, dep, mapId, context, partition) //99
write方法中:
var writer: ShuffleWriter[Any, Any] = null
val manager = SparkEnv.get.shuffleManager
// shuffle以什么形式写出,由shuffleManager决定,可由自己配置
writer = manager.getWriter
//执行写出操作
writer.write(
rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]]) //59
iterator方法中:
//判断是否使用了缓存
if (storageLevel != StorageLevel.NONE) {
//如果有缓存,直接从缓存中获取EDD
getOrCompute(split, context)
} else {
//如果没有缓存,尝试从checkpoint目录中获取RDD,如果没有就计算得到
computeOrReadCheckpoint(split, context)
}
'ResultTask:
runTask():
//这个func是用户调用行动算子时的逻辑函数
func(context, rdd.iterator(partition, context)) // 90
"至此,job的提交流程便结束了"