DAGScheduler

DAGScheduler的主要任务是基于Stage构建DAG,决定每个任务的最佳位置

  • 记录哪个RDD或者Stage输出被物化
  • 面向stage的调度层。为job生成以stage组成的DAG。提交TaskSet给TaskScheduler运行
  • 又一次提交shuffle输出丢失的stage

每个Stage内。都是独立的tasks,他们共同运行同一个computefunction,享有同样的shuffledependencies。DAG在切分stage的时候是按照出现shuffle为界限的。

DAGScheduler实例化

以下的代码是SparkContext实例化DAGScheduler的过程:

  1. @volatile private[spark] var dagScheduler: DAGScheduler = _
  2. try {
  3. dagScheduler = new DAGScheduler(this)
  4. } catch {
  5. case e: Exception => {
  6. try {
  7. stop()
  8. } finally {
  9. throw new SparkException("Error while constructing DAGScheduler", e)
  10. }
  11. }
  12. }

以下代码显示了DAGScheduler的构造函数定义中,通过绑定TaskScheduler的方式创建,当中次构造函数去调用主构造函数来将sc的字段填充入參:

  1. private[spark]
  2. class DAGScheduler(
  3. private[scheduler] val sc: SparkContext,
  4. private[scheduler] val taskScheduler: TaskScheduler,
  5. listenerBus: LiveListenerBus,
  6. mapOutputTracker: MapOutputTrackerMaster,
  7. blockManagerMaster: BlockManagerMaster,
  8. env: SparkEnv,
  9. clock: Clock = new SystemClock())
  10. extends Logging {
  11. def this(sc: SparkContext, taskScheduler: TaskScheduler) = {
  12. this(
  13. sc,
  14. taskScheduler,
  15. sc.listenerBus,
  16. sc.env.mapOutputTracker.asInstanceOf[MapOutputTrackerMaster],
  17. sc.env.blockManager.master,
  18. sc.env)
  19. }
  20. def this(sc: SparkContext) = this(sc, sc.taskScheduler)

作业提交与DAGScheduler操作

Action的大部分操作会进行作业(job)的提交,源代码1.0版的job提交过程的大致调用链是:sc.runJob()–>dagScheduler.runJob–>dagScheduler.submitJob—>dagSchedulerEventProcessActor.JobSubmitted–>dagScheduler.handleJobSubmitted–>dagScheduler.submitStage–>dagScheduler.submitMissingTasks–>taskScheduler.submitTasks

详细的作业提交运行期的函数调用为:

  1. sc.runJob->dagScheduler.runJob->submitJob
  2. DAGScheduler::submitJob会创建JobSummitted的event发送给内嵌类eventProcessActor(在源代码1.4中,submitJob函数中,使用DAGSchedulerEventProcessLoop类进行事件的处理)
  3. eventProcessActor在接收到JobSubmmitted之后调用processEvent处理函数
  4. job到stage的转换,生成finalStage并提交运行。关键是调用submitStage
  5. 在submitStage中会计算stage之间的依赖关系,依赖关系分为宽依赖和窄依赖两种
  6. 假设计算中发现当前的stage没有不论什么依赖或者全部的依赖都已经准备完毕,则提交task
  7. 提交task是调用函数submitMissingTasks来完毕
  8. task真正运行在哪个worker上面是由TaskScheduler来管理,也就是上面的submitMissingTasks会调用TaskScheduler::submitTasks
  9. TaskSchedulerImpl中会依据Spark的当前运行模式来创建对应的backend,假设是在单机运行则创建LocalBackend
  10. LocalBackend收到TaskSchedulerImpl传递进来的ReceiveOffers事件
  11. receiveOffers->executor.launchTask->TaskRunner.run

DAGScheduler的runJob函数

DAGScheduler.runjob最后把结果通过resultHandler保存返回。

这里DAGScheduler的runJob函数调用DAGScheduler的submitJob函数来提交任务:

  1. def runJob[T, U: ClassTag](
  2. rdd: RDD[T],
  3. func: (TaskContext, Iterator[T]) => U,
  4. partitions: Seq[Int],
  5. callSite: CallSite,
  6. allowLocal: Boolean,
  7. resultHandler: (Int, U) => Unit,
  8. properties: Properties): Unit = {
  9. val start = System.nanoTime
  10. val waiter = submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler, properties)
  11. waiter.awaitResult() match {
  12. case JobSucceeded => {
  13. logInfo("Job %d finished: %s, took %f s".format
  14. (waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9))
  15. }
  16. case JobFailed(exception: Exception) =>
  17. logInfo("Job %d failed: %s, took %f s".format
  18. (waiter.jobId, callSite.shortForm, (System.nanoTime - start) / 1e9))
  19. throw exception
  20. }
  21. }

作业提交的调度

在Spark源代码1.4.0中,DAGScheduler的submitJob函数不再使用DAGEventProcessActor进行事件处理和消息通信,而是使用DAGSchedulerEventProcessLoop类实例eventProcessLoop进行JobSubmitted事件的post动作。

以下是submitJob函数代码:

  1. /**
  2. * Submit a job to the job scheduler and get a JobWaiter object back. The JobWaiter object
  3. * can be used to block until the the job finishes executing or can be used to cancel the job.
  4. */
  5. def submitJob[T, U](
  6. rdd: RDD[T],
  7. func: (TaskContext, Iterator[T]) => U,
  8. partitions: Seq[Int],
  9. callSite: CallSite,
  10. allowLocal: Boolean,
  11. resultHandler: (Int, U) => Unit,
  12. properties: Properties): JobWaiter[U] = {
  13. // Check to make sure we are not launching a task on a partition that does not exist.
  14. val maxPartitions = rdd.partitions.length
  15. partitions.find(p => p >= maxPartitions || p < 0).foreach { p =>
  16. throw new IllegalArgumentException(
  17. "Attempting to access a non-existent partition: " + p + ". " +
  18. "Total number of partitions: " + maxPartitions)
  19. }
  20. val jobId = nextJobId.getAndIncrement()
  21. if (partitions.size == 0) {
  22. return new JobWaiter[U](this, jobId, 0, resultHandler)
  23. }
  24. assert(partitions.size > 0)
  25. val func2 = func.asInstanceOf[(TaskContext, Iterator[_]) => _]
  26. val waiter = new JobWaiter(this, jobId, partitions.size, resultHandler)
  27. eventProcessLoop.post(JobSubmitted(
  28. jobId, rdd, func2, partitions.toArray, allowLocal, callSite, waiter, properties))
  29. waiter
  30. }

当eventProcessLoop对象投递了JobSubmitted事件之后,对象内的eventThread线程实例对事件进行处理。不断从事件队列中取出事件,调用onReceive函数处理事件。当匹配到JobSubmitted事件后。调用DAGScheduler的handleJobSubmitted函数并传入jobid、rdd等參数来处理Job。

handleJobSubmitted函数

Job处理过程中handleJobSubmitted比較关键,该函数主要负责RDD的依赖性分析。生成finalStage,并依据finalStage来产生ActiveJob。

在handleJobSubmitted函数源代码中。给出了部分凝视:

  1. private[scheduler] def handleJobSubmitted(jobId: Int,
  2. finalRDD: RDD[_],
  3. func: (TaskContext, Iterator[_]) => _,
  4. partitions: Array[Int],
  5. allowLocal: Boolean,
  6. callSite: CallSite,
  7. listener: JobListener,
  8. properties: Properties) {
  9. var finalStage: Stage = null
  10. try {
  11. // New stage creation may throw an exception if, for example, jobs are run on a
  12. // HadoopRDD whose underlying HDFS files have been deleted.
  13. finalStage = newStage(finalRDD, partitions.size, None, jobId, callSite)
  14. } catch {
  15. //错误处理,告诉监听器作业失败。返回....
  16. case e: Exception =>
  17. logWarning("Creating new stage failed due to exception - job: " + jobId, e)
  18. listener.jobFailed(e)
  19. return
  20. }
  21. if (finalStage != null) {
  22. val job = new ActiveJob(jobId, finalStage, func, partitions, callSite, listener, properties)
  23. clearCacheLocs()
  24. logInfo("Got job %s (%s) with %d output partitions (allowLocal=%s)".format(
  25. job.jobId, callSite.shortForm, partitions.length, allowLocal))
  26. logInfo("Final stage: " + finalStage + "(" + finalStage.name + ")")
  27. logInfo("Parents of final stage: " + finalStage.parents)
  28. logInfo("Missing parents: " + getMissingParentStages(finalStage))
  29. val shouldRunLocally =
  30. localExecutionEnabled && allowLocal && finalStage.parents.isEmpty && partitions.length == 1
  31. val jobSubmissionTime = clock.getTimeMillis()
  32. if (shouldRunLocally) {
  33. // 非常短、没有父stage的本地操作,比方 first() or take() 的操作本地运行
  34. // Compute very short actions like first() or take() with no parent stages locally.
  35. listenerBus.post(
  36. SparkListenerJobStart(job.jobId, jobSubmissionTime, Seq.empty, properties))
  37. runLocally(job)
  38. } else {
  39. // collect等操作走的是这个过程。更新相关的关系映射,用监听器监听,然后提交作业
  40. jobIdToActiveJob(jobId) = job
  41. activeJobs += job
  42. finalStage.resultOfJob = Some(job)
  43. val stageIds = jobIdToStageIds(jobId).toArray
  44. val stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))
  45. listenerBus.post(
  46. SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))
  47. // 提交stage
  48. submitStage(finalStage)
  49. }
  50. }
  51. // 提交stage
  52. submitWaitingStages()
  53. }

小结

该篇文章介绍了DAGScheduler从SparkContext中进行实例化,到运行Action操作时提交任务调用runJob函数,进而介绍了提交任务的消息调度,和处理Job函数handleJobSubmitted函数。

因为在handleJobSubmitted函数中涉及到依赖性分析和stage的源代码内容,于是我计划在下一篇文章里进行介绍和源代码分析。

转载请注明作者Jason Ding及其出处

GitCafe博客主页(http://jasonding1354.gitcafe.io/)

Github博客主页(http://jasonding1354.github.io/)

CSDN博客(http://blog.csdn.net/jasonding1354)

简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)

Google搜索jasonding1354进入我的博客主页

【Spark】DAGScheduler源代码浅析的更多相关文章

  1. 【Spark】Stage生成和Stage源代码浅析

    引入 上一篇文章<DAGScheduler源代码浅析>中,介绍了handleJobSubmitted函数,它作为生成finalStage的重要函数存在.这一篇文章中,我将就DAGSched ...

  2. 【Spark Core】任务运行机制和Task源代码浅析1

    引言 上一小节<TaskScheduler源代码与任务提交原理浅析2>介绍了Driver側将Stage进行划分.依据Executor闲置情况分发任务,终于通过DriverActor向exe ...

  3. Spark SQL 源代码分析之 In-Memory Columnar Storage 之 in-memory query

    /** Spark SQL源代码分析系列文章*/ 前面讲到了Spark SQL In-Memory Columnar Storage的存储结构是基于列存储的. 那么基于以上存储结构,我们查询cache ...

  4. Spark SQL 源代码分析系列

    从决定写Spark SQL文章的源代码分析,到现在一个月的时间,一个又一个几乎相同的结束很快,在这里也做了一个综合指数,方便阅读,下面是读取顺序 :) 第一章 Spark SQL源代码分析之核心流程 ...

  5. Spark SQL源代码分析之核心流程

    /** Spark SQL源代码分析系列文章*/ 自从去年Spark Submit 2013 Michael Armbrust分享了他的Catalyst,到至今1年多了,Spark SQL的贡献者从几 ...

  6. Gradle 庖丁解牛(构建生命周期核心托付对象创建源代码浅析)

    [工匠若水 http://blog.csdn.net/yanbober 未经同意严禁转载,请尊重作者劳动成果.私信联系我] 1 背景 上一篇<Gradle 庖丁解牛(构建源头源代码浅析)> ...

  7. Spark SQL 源代码分析之Physical Plan 到 RDD的详细实现

    /** Spark SQL源代码分析系列文章*/ 接上一篇文章Spark SQL Catalyst源代码分析之Physical Plan.本文将介绍Physical Plan的toRDD的详细实现细节 ...

  8. Spark Core源代码分析: Spark任务运行模型

    DAGScheduler 面向stage的调度层,为job生成以stage组成的DAG,提交TaskSet给TaskScheduler运行. 每个Stage内,都是独立的tasks,他们共同运行同一个 ...

  9. Android网络通信Volley框架源代码浅析(三)

    尊重原创 http://write.blog.csdn.net/postedit/26002961 通过前面浅析(一)和浅析(二)的分析.相信大家对于Volley有了初步的认识,可是假设想更深入的理解 ...

随机推荐

  1. Introduction of Version Control/Git, SVN

    什么是版本控制? 你可以把一个版本控制系统(缩写VCS)理解为一个“数据库”,在需要的时候,它可以帮你完整地保存一个项目的快照.当你需要查看一个之前的快照(称之为“版本”)时,版本控制系统可以显示出当 ...

  2. 15 AJAX

     AJAX AJAX 简介 AJAX 是 异步 JavaScript 及 XML(Asynchronous JavaScript and XML)的缩写.AJAX 不是一种新的编程语言,而是一种用于创 ...

  3. 10Oracle Database 数据表数据查询

    Oracle Database 数据表数据查询 DML 数据操纵语言 - 数据的查看和维护 select / insert /delete /update 基本查询语句 Select [distinc ...

  4. oracle文件 结构01

    1.减少数据的冗余(例如使用id) 2.保证数据库一致性 关联表越多越慢 主机名 hosts 文件 ntp 时间同步

  5. JAVA基础——异常--解析

      简介 异常处理是java语言的重要特性之一,<Three Rules for effective Exception Handling>一文中是这么解释的:它主要帮助我们在debug的 ...

  6. 【反向并查集、联通图】P1197 [JSOI2008]星球大战

    题目描述 很久以前,在一个遥远的星系,一个黑暗的帝国靠着它的超级武器统治着整个星系. 某一天,凭着一个偶然的机遇,一支反抗军摧毁了帝国的超级武器,并攻下了星系中几乎所有的星球.这些星球通过特殊的以太隧 ...

  7. Luogu P4016 「 网络流 24 题 」负载平衡问题

    吐槽题目难度,这个题建模好像比前两个都要难,但是难度评级却比第二个要低. 解题思路 依旧是考虑如何建模和建立源点汇点.每个点的货物数量到最后都一样的话肯定是等于他们的平均值.用 $num$ 数组存储原 ...

  8. 为什么map对象不能使用stl中的sort函数

    STL所提供的各式各样算法中,sort()是最复杂最庞大的一个.这个算法接受两个RandomAccestlerators(随机存取迭代器),然后将区间内的所有元素以渐增方式由小到大重新排列.第二个版本 ...

  9. 洛谷 P1280 尼克的任务 (线性DP)

    题意概括 线性资源分配的问题,因为空闲的时间大小看后面的时间(反正感觉这个就是个套路)所以从后往前DP. 转移方程 如果当前时刻没有工作 f[i]=f[i+1]+1 如果当前时刻有工作 f[i]=ma ...

  10. pxc增量备份

    ###增备数据库,如果后续还需要再次增备,则可以再次指定--extra-lsndir,如果与上次备份指定相同的位置,该文件被覆盖# innobackupex --compress --incremen ...