spark-源碼-action算子觸發

基于spark1.6

創建完SparkContext,然后執行Action算子

當RDD執行Action算子時(形成一個job),會將代碼提交到Master上運行,

例如wordcount的action 算子 collect方法? def collect(): Array[T] = {

? ? val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)

? ? Array.concat(results: _*)

? }

sc是SparkContext對象,上面 runJob 如下

? def runJob[T, U: ClassTag](

? ? ? rdd: RDD[T],

? ? ? func: (TaskContext, Iterator[T]) => U,

? ? ? partitions: Seq[Int],

? ? ? allowLocal: Boolean,

? ? ? resultHandler: (Int, U) => Unit) {

........................

? ? }

? ? //該方法調用多次重載的方法后,最終會調用dagScheduler的runJob,形成和切分stage

def runJob[T, U: ClassTag](

? ? ? rdd: RDD[T],

? ? ? func: (TaskContext, Iterator[T]) => U,

? ? ? partitions: Seq[Int],

? ? ? allowLocal: Boolean,

? ? ? resultHandler: (Int, U) => Unit) {

? 。。。。。。。

? ? //dagScheduler出現了,可以切分stage

? ? dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, allowLocal,

? ? ? resultHandler, localProperties.get)

? ? progressBar.foreach(_.finishAll())

? ? rdd.doCheckpoint()

? }


dagScheduler的runJob 是我們比較關心的

def runJob[T, U: ClassTag](

? ? 。。。。。

? ? val waiter = submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler, properties)

? }

這里面的我們主要看的是submitJob(rdd, func, partitions, callSite, allowLocal, resultHandler, properties)提交任務,括號里面的是任務信息

def submitJob[T, U](。。。): JobWaiter[U] = {

? ? //在這兒才封裝任務提交事件,把該事件對象加入到任務隊列里面? ? eventProcessLoop.post(JobSubmitted(

? ? ? jobId, rdd, func2, partitions.toArray, allowLocal, callSite, waiter, properties))

? }

JobSubmitted:// 封裝job事件對象,放入DAGScheduler阻塞的事件隊列,例如:任務id,數據RDD,fun,jobId(可見一個action就是一個job)

從隊列中取出事件對象,調用 onReceive方法,即調用子類 DAGSchedulerEventProcessLoop 的onReceive方法,該方法的匹配模式如下:

(1)先生成finalStage。

? ? case JobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties) =>

? ? ? //調用dagScheduler來出來提交任務

? ? ? dagScheduler.handleJobSubmitted(jobId, rdd, func, partitions, allowLocal, callSite, listener, properties)

調用了handleJobSubmitted方法,接下來查看該方法

private[scheduler] def handleJobSubmitted(jobId: Int,

? ? ? finalRDD: RDD[_],

? ? ? func: (TaskContext, Iterator[_]) => _,

? ? ? partitions: Array[Int],

? ? ? allowLocal: Boolean,

? ? ? callSite: CallSite,? ? ? listener: JobListener,

? ? ? properties: Properties) {

? ? ? ? var finalStage: Stage = null

? ? ? ? //最終的stage,從后往前劃分

? ? ? ? finalStage = newResultStage(finalRDD, partitions.size, None, jobId, callSite)

? ? ? ? 。。。。

? ? ? ? submitStage(finalStage)

? //?提交其他正在等待的stage??

?? submitWaitingStages()?}

}

/**??

???*?創建一個 ResultStage?,形成有向無環圖

???*/??

??private?def?newResultStage(??

rdd:?RDD[_],

??????func:?(TaskContext,?Iterator[_])?=>?_,??

??????partitions:?Array[Int],??

??????jobId:?Int,??

??????callSite:?CallSite):?ResultStage?=?{??

?//下面這個函數會生成我們的DAG,需重點關注??

????val?(parentStages:?List[Stage],?id:?Int)?=?getParentStagesAndId(rdd,?jobId)??

val?stage?=?new?ResultStage(id,rdd,?func,?partitions,parentStages,?jobId,?callSite)

????stageIdToStage(id)?=?stage?//將Stage的id放入stageIdToStage結構中。??

????updateJobIdStageIdMaps(jobId,?stage)?//更新JobIdStageIdMaps??

????stage??

?}

??}?

上面的代碼中,調用了newResultStage方法,該方法是劃分任務的核心方法,任務劃分是根據最后一個依賴關系作為開始,通過遞歸,將每個寬依賴做為切分Stage的依據,切分Stage的過程是流程中的一環(詳見 day29_spark-源碼-Stage劃分算法,并最終得到了DAG圖中的Result Stage(final Stage)),但在這里不詳細闡述,當任務切分完畢后,代碼繼續執行來到submitStage(finalStage)這里開始進行任務提交

(2)提交resultStage

//提交Stage,如果有未提交的ParentStage,則會遞歸提交這些ParentStage,只有所有ParentStage都計算完了,才能提交當前Stage?

? private def submitStage(stage: Stage) { // 此stage是 result stage

? //?根據stage獲取jobId??

? ? val jobId = activeJobForStage(stage)? //查找該Stage的所有激活的job

? if (jobId.isDefined) {? //?jobId 存在就執行,如果不存在就停止??

//?記錄Debug日志信息:submitStage(stage)??

? ? ? logDebug("submitStage(" + stage + ")")?

? ? //如果當前Stage沒有在等待parent Stage的返回,也不是正在運行的Stage,并且也沒有提 示提交失敗,說明未處理,那么我們就嘗試提交Stage?

? ? ? if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {?

? ? ? ? //得到還未執行的父 stage

val missing = getMissingParentStages(stage).sortBy(_.id)?

? ? ? ? logDebug("missing: " + missing)?

? ? ? ? if (missing.isEmpty) {? ? //如果沒有父 Stage?

? ? ? ? //當前stage 拆分成task,形成taskSet 并提交

? ? ? ? ? submitMissingTasks(stage, jobId.get) // 注意這個stage會是兩種類型 1、shufflerMapStage 2、resultStage

? ? ? ? ? ? } else {?

? ? ? ? ? //有父Stage沒進行計算,就遞歸提交這些父Stage?

? ? ? ? ? for (parent <- missing) {? // 該stage的所有父stage

? ? ? ? ? ? submitStage(parent)// 遞歸調用本身?

? ? }?

? ? ? ? ? ? waitingStages += stage?

? ? ? ? }?

? ? ? }?

? ? } else {//無效作業,停止它。?

? ? ? abortStage(stage, "No active job for stage " + stage.id, None)?

? ? }?

}

********************getMissingParentStages方法如下****************

針對 stage的執行要記住2個判斷點 1、getmissingParentStages()方法為核心方法。這里我們要懂得這樣一個邏輯:我們都知道,Stage是通過shuffle劃分的,所以,每一Stage都是以shuffle開始的,若一個RDD是寬依賴,則必然說明該RDD的父RDD在另一個Stage中,若一個RDD是窄依賴,則該RDD所依賴的父RDD還在同一個Stage中,我們可以根據這個邏輯,找到該Stage的父Stage。

// DAGScheduler.scala

private def getMissingParentStages(stage: Stage): List[Stage] = {

? ? val missing = new HashSet[Stage] //用于存放父Stage

? ? val visited = new HashSet[RDD[_]] //用于存放已訪問過的RDD

? ? val waitingForVisit = new Stack[RDD[_]]

? ? def visit(rdd: RDD[_]) {

? ? ? if (!visited(rdd)) { //如果RDD沒有被訪問過,則進行訪問

? ? ? ? visited += rdd //添加到已訪問RDD的HashSet中

? ? ? ? val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)

? ? ? ? if (rddHasUncachedPartitions) {

? ? ? ? ? for (dep <- rdd.dependencies) { //獲取該RDD的依賴

? ? ? ? ? ? dep match {

? ? ? ? ? ? ? case shufDep: ShuffleDependency[_, _, _] =>//若為寬依賴,則該RDD依賴的RDD所在的stage必為父stage

? ? ? ? ? ? ? ? val mapStage = getOrCreateShuffleMapStage(shufDep, stage.firstJobId)//生成父Stage

? ? ? ? ? ? ? ? if (!mapStage.isAvailable) {//若父Stage task沒有完全執行,則添加到父Stage的HashSET中

? ? ? ? ? ? ? ? ? missing += mapStage // 如果是寬依賴,那么就表示找到了,不存在寬依賴前還有寬依賴

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? case narrowDep: NarrowDependency[_] =>//若為窄依賴,則需要再判斷,其父有無寬依賴

? ? ? ? ? ? ? ? waitingForVisit.push(narrowDep.rdd)

? ? ? ? ? ? }

? ? ? ? ? }

? ? ? ? }

? ? ? }

? ? }

? ? waitingForVisit.push(stage.rdd)

? ? while (waitingForVisit.nonEmpty) {//循環遍歷所有RDD

? ? ? visit(waitingForVisit.pop())

? ? }

? ? missing.toList

}

def isAvailable: Boolean = _numAvailableOutputs == numPartitions

針對 stage的執行要記住2個判斷點 2、每當執行完一個Task會對變量_numAvailableOutputs加1,直至所有Task執行完,_numAvailableOutputs等于分區數。

(3)提交MissingTask

stage根據 parition 拆分成task(決定每個Task的最佳位置)生成TaskSet,并提交到TaskScheduler

private def submitMissingTasks(stage: Stage, jobId: Int) {

? //首先根據stage所依賴的RDD的partition的分布,會產生出與partition數量相等的task

? var tasks = ArrayBuffer[Task[_]]()

? //對于resultStage或是shufflerMapStage會產生不同的task。

? //檢查該stage時是否ShuffleMapStage,如果是則生成ShuffleMapTask

? if (stage.isShuffleMapStage) { //生成ShuffleMapStage

? ? for (p <- 0 until stage.numPartitions if stage.outputLocs(p) == Nil) {

? ? ? //task根據partition的locality進行分布

? ? ? val locs = getPreferredLocs(stage.rdd, p)

? ? ? tasks += new ShuffleMapTask(stage.id, stage.rdd, stage.shuffleDep.get, p, locs)

? ? }

? } else { //resultStage:該類型stage直接輸出結果生成ResultTask

? val job = resultStageToJob(stage)

? ? for (id <- 0 until job.numPartitions if !job.finished(id)) {

? ? ? val partition = job.partitions(id)

? ? ? val locs = getPreferredLocs(stage.rdd, partition)

? ? ? //由于是ResultTask,因此需要傳入定義的func,也就是處理結果返回

? ? ? tasks += new ResultTask(stage.id, stage.rdd, job.func, partition, locs, id)

? ? }

? }

? //向TaskSchuduler提交任務,以stage為單位,一個stage對應一個TaskSet

? taskScheduler.submitTasks(new TaskSet(tasks.toArray, stage.id, stage.newAttemptId(), stage.jobId, properties))

}

Task任務調度

taskScheduler.submitTasks方法比較重要,主要將任務加入調度池(taskschduler 創建時初始一個調度池),最后調用了CoarseGrainedSchedulerBackend.reviveOffers()

override def submitTasks(taskSet: TaskSet) {

? ? val tasks = taskSet.tasks

? ? this.synchronized {

? ? ? ? //將TaskSet 封裝成TaskSetManger? ? ? val manager = createTaskSetManager(taskSet, maxTaskFailures)

? ? ? activeTaskSets(taskSet.id) = manager

? ? //用 schedulableBuilder去添加TaskSetManager到隊列中

//schedulableBuilder有兩種形態:FIFOSchedulableBuilder: 單一pool ,FairSchedulableBuilder: ? 多個pool? ? ? schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties)

。。。。。。。。。。。

? ? //在TaskSchedulerImpl在submitTasks添加TaskSetManager到pool后,調用了? ? backend.reviveOffers()? ? ?

//fifo 直接將可調度對象TaskSetManager加入SchedulerQueue的尾端。?

override def addSchedulable(schedulable: Schedulable) {?

? ? require(schedulable != null)?

? ? schedulableQueue.add(schedulable)?

? ? schedulableNameToSchedulable.put(schedulable.name, schedulable)?

? ? schedulable.parent = this?

? }

? override def reviveOffers() {

? ? //自己給自己發消息(告訴它我要提交task)

? ? driverActor ! ReviveOffers? }

這里用了內部的DriverActor對象發送了一個內部消息給自己,接下來查看receiver方法接受的消息

收到消息后調用了makeOffers()方法

? ? ? case ReviveOffers =>

? ? ? ? makeOffers()

? def makeOffers() {

? ? ? launchTasks(scheduler.resourceOffers(executorDataMap.map {

? case (id, executorData) =>

? ? ? ? new WorkerOffer(id, executorData.executorHost, executorData.freeCores)

? ? ? }.toSeq))

? ? }

makeOffers方法中,將Executor的信息集合與調度池中的Tasks封裝成WokerOffers,調用

launchTasks

? ? def launchTasks(tasks: Seq[Seq[TaskDescription]]) {

? ? ? for (task <- tasks.flatten) {

? ? ? ? 。。。。。。

? ? ? ? //把task序列化

? ? ? ? val serializedTask = ser.serialize(task)

? ? ? ? ? ? 。。。。。

//向executor進程 發送創建TaskRunner(extends Runnable)

? ? ? ? ? val executorData = executorDataMap(task.executorId)(這是之前注冊過了的)

? ? ? ? ? executorData.freeCores -= scheduler.CPUS_PER_TASK

? ? ? ? ? //把序列化好的task發送給Executor

executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))? ? ? ? ?

? ? ? ? }

? ? ? }

? ? }

會由CoarseGrainedSchedulerBackend來接受執行指令,內部封裝DriverActor

launchTasks方法將遍歷Tasks集合,每個Task任務序列化,發送啟動Task執行消息的給Executor

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380