Spark - DAGScheduler

在Spark中有几个重要概念:

  • Application - 源代码就是应用
  • Job - action会触发一个job
  • Stage - 按照宽窄依赖来分的
  • Task - 最终执行的工作
  • Driver - 跑源代码main func,跑各种并行操作的机器
  • Executor - 执行task细节的机器

我们从以下简单的一行代码入手,来看spark中的各个术语的含义。

scala>sc.textFile("README.md").filter(_.contains("Spark")).count

其中,sc代表SparkContext,它通过一些default的SparkConfig构建出来。这一行代码就算是一个Application。sc通过textFile, filter操作RDDs,最后count这个Action触发一个job。


整体上描述一下spark的运行:Application会运行在driver上,driver会根据代码中的action创建并提交job(runJob/submitJob)。然后从job的最后一个RDD朝前演算,遇到一个宽依赖就创建一个stage。最后以stage为单位创建task集合,并在excutor中执行每项task

spark拆分任务的流程图如下:

runJob flow.png

涉及到的几个class:
SparkContext, DAGScheduler, DAGSchedulerEventProcessLoop, TaskScheduler

几个class的相互关系

  1. SparkContext中初始化DAGScheduler, TaskScheduler
  2. DAGScheduler中初始化DAGSchedulerEventProcessLoop(eventProcessLoop)
  3. DAGScheduler的构造函数参数中包含TaskScheduler

流程介绍

整个过程就是将RDD DAG按照宽窄依赖切分成Stage DAG:

  • 首先在SparkContext初始化的时候会创建DAGScheduler,这个DAGScheduler每个应用只有一个。然后DAGScheduler创建的时候,会初始化一个事件捕获对象DAGSchedulerEventProcessLoop,并且开启监听(start)。之后我们的任务都会发给这个事件监听器,它会按照任务的类型创建不同的任务(doOnReceive)。
  • 再从客户端程序方面说,当我们调用action操作的时候,就会触发runJob,它内部其实就是向前面的那个事件监听器提交一个任务。
  • 然后事件监听器调用DAGScheduler的handleJobSubmitted做真正的处理
  • 处理的时候,要去创建一个ResultStage(每个job只有一个ResultStage,其余的都是ShuffleMapStage),这会根据RDD的依赖关系,按照广度优先(总是先找到自己的所有直接parents)的思想遍历所有RDD,遇到ShuffleRDD就创建一个新的stage,最终形成一个以ResultStage为尾的stage DAG(透过访问ResultStage,朝前不停遍历就可以找到所有的stage)
  • 形成stage DAG图后,遍历等待执行的stage列表,如果这个stage所依赖的父stage执行完了,它就可以执行了;否则还需要继续等待。
  • 最终stage会以taskset的形式,提交给TaskScheduler,最后提交给excutor。
 private def getMissingAncestorShuffleDependencies(
      rdd: RDD[_]): Stack[ShuffleDependency[_, _, _]] = {
    val ancestors = new Stack[ShuffleDependency[_, _, _]]
    val visited = new HashSet[RDD[_]]
    // We are manually maintaining a stack here to prevent StackOverflowError
    // caused by recursively visiting
    val waitingForVisit = new Stack[RDD[_]]
    waitingForVisit.push(rdd)
    while (waitingForVisit.nonEmpty) {
      val toVisit = waitingForVisit.pop()
      if (!visited(toVisit)) {
        visited += toVisit
        getShuffleDependencies(toVisit).foreach { shuffleDep =>
          if (!shuffleIdToMapStage.contains(shuffleDep.shuffleId)) {
            ancestors.push(shuffleDep)    //广度遍历
            waitingForVisit.push(shuffleDep.rdd)
          } // Otherwise, the dependency and its ancestors have already been registered.
        }
      }
    }
    ancestors
  }

参考: http://www.cnblogs.com/xing901022/p/6674966.html

stage拆分的整个函数调用过程如下:

stage creation flow.jpeg

举例说明:
如下图,spark job依赖关系:

job.jpg

上图抽象如下:

[E] <--------------
                    \
[C] <------[D]------[F]--(s_F)----
                                   \
[A] <-----(s_A)----- [B] <-------- [G]

Note: [] means an RDD, () means a shuffle dependency.

结果解析

  • 对 G 调用 creatResultStage,先为所有parent创建ShuffleMapStage,然后创建本身的 ResultStage。 如上图getOrCreateParentStages会先创建上游 stage1和stage2, 然后创建自己的 stage3

  • getOrCreateParentStages 会调用 getShuffleDependencies 获得 rdd_G 所有直接宽依赖 HashSet(s_F, s_A), 然后遍历集合,对 s_F 和 s_A 调用 getOrCreateShuffleMapStage

  • 对 s_A 调用 getOrCreateShuffleMapStage, shuffleIdToMapStage 中获取判断为None, 对 rdd_A 调用getMissingAncestorShuffleDependencies, 返回为空, 对 s_A 调用 createShuffleMapStage, 由于rdd_A 没有parent stage 直接就创建 stage1 返回

  • 对 s_F 调用 getOrCreateShuffleMapStage, shuffleIdToMapStage 中获取判断为None, 对 rdd_F 调用 getMissingAncestorShuffleDependencies, 返回为空, 对 s_F 调用 createShuffleMapStage, 由于rdd_F 没有parent stage 直接就创建 stage2 返回

  • 把 List(stage1,stage2) 作为 stage3 的 parents stages 创建 stage3


至此,Stage都建立起来之后,就要开始执行各个stage

submitStage -> getMissingParentStages -> submitMissingTasks -> submitStage

整体上来讲:

  • 在submitStage中会计算stage之间的依赖关系,依赖关系分为宽依赖和窄依赖两种
  • 如果计算中发现当前的stage没有任何依赖或者所有的依赖都已经准备完毕,则提交task
  • 提交task是调用函数submitMissingTasks来完成
    -当前stage执行完毕之后,再调用函数submitStage来执行child stage

TaskScheduler在SparkContext初始化期间就会初始化并且start,其backend会根据deploy mode作相应调整

submitMissingTasks -> taskScheduler(TaskSchedulerImpl).submitTasks -> backend.reviveOffers -> executor.launchTask -> threadPool.execute

private def submitMissingTasks(stage: Stage, jobId: Int) {
        ......
        ......
    val tasks: Seq[Task[_]] = try {
      val serializedTaskMetrics = closureSerializer.serialize(stage.latestInfo.taskMetrics).array()
      stage match {
        case stage: ShuffleMapStage =>
          stage.pendingPartitions.clear()
          partitionsToCompute.map { id =>
            val locs = taskIdToLocations(id)
            val part = stage.rdd.partitions(id)
            stage.pendingPartitions += id
            new ShuffleMapTask(stage.id, stage.latestInfo.attemptId,
              taskBinary, part, locs, properties, serializedTaskMetrics, Option(jobId),
              Option(sc.applicationId), sc.applicationAttemptId)
          }

        case stage: ResultStage =>
          partitionsToCompute.map { id =>
            val p: Int = stage.partitions(id)
            val part = stage.rdd.partitions(p)
            val locs = taskIdToLocations(id)
            new ResultTask(stage.id, stage.latestInfo.attemptId,
              taskBinary, part, locs, id, properties, serializedTaskMetrics,
              Option(jobId), Option(sc.applicationId), sc.applicationAttemptId)
          }
      }
    } catch {
      case NonFatal(e) =>
        abortStage(stage, s"Task creation failed: $e\n${Utils.exceptionString(e)}", Some(e))
        runningStages -= stage
        return
    }
    if (tasks.size > 0) {
      logInfo(s"Submitting ${tasks.size} missing tasks from $stage (${stage.rdd}) (first 15 " +
        s"tasks are for partitions ${tasks.take(15).map(_.partitionId)})")
      taskScheduler.submitTasks(new TaskSet(
        tasks.toArray, stage.id, stage.latestInfo.attemptId, jobId, properties))
    } else {
      // Because we posted SparkListenerStageSubmitted earlier, we should mark
      // the stage as completed here in case there are no tasks to run
      markStageAsFinished(stage, None)

      val debugString = stage match {
        case stage: ShuffleMapStage =>
          s"Stage ${stage} is actually done; " +
            s"(available: ${stage.isAvailable}," +
            s"available outputs: ${stage.numAvailableOutputs}," +
            s"partitions: ${stage.numPartitions})"
        case stage : ResultStage =>
          s"Stage ${stage} is actually done; (partitions: ${stage.numPartitions})"
      }
      logDebug(debugString)

      submitWaitingChildStages(stage)
    }

注意:

  1. stageID从1开始,按照"爷->父->子->孙"一次递增1
private val nextStageId = new AtomicInteger(0)
val id = nextStageId.getAndIncrement()
  1. 每个job只有一个ResultStage,其余的都是ShuffleMapStage
  2. 每个Stage的实例中,都包含一个parents的属性,这样就可以透过"孙"stage朝前找到所有的"祖先"stage
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266