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閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374