Spark Job提交的整体流程源码详解

2 篇文章 0 订阅
2 篇文章 0 订阅

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的提交流程便结束了"

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值