引言
上篇 spark 原始碼分析之十九 -- DAG的生成和Stage的劃分 中,主要介紹了下圖中的前兩個階段DAG的構建和Stage的劃分。
本篇文章主要剖析,Stage是如何提交的。
rdd的依賴關係構成了DAG,DAGScheduler根據shuffle依賴關係將DAG圖劃分為一個一個小的stage。具體可以看 spark 原始碼分析之十九 -- DAG的生成和Stage的劃分 做進一步瞭解。
緊接上篇文章
上篇文章中,DAGScheduler的handleJobSubmitted方法我們只剖析了stage的生成部分,下面我們看一下stage的提交部分原始碼。
提交Stage的思路
首先構造ActiveJob物件,其次清除快取的block location資訊,然後記錄jobId和job物件的對映關係到jobIdToActiveJob map集合中,並且將該jobId記錄到活動的job集合中。
獲取到Job所有的stage的唯一標識,並且根據唯一標識來獲取stage物件,並且呼叫其lastestInfo方法獲取其StageInfo物件。
然後進一步封裝成 SparkListenerJobStart 事件物件,並post到 listenerBus中,listenerBus 是一個 LiveListenerBus 物件,其內部封裝了四個訊息佇列組成的集合,具體可以看 spark 原始碼分析之三 -- LiveListenerBus介紹 文章做進一步瞭解。
最後呼叫submitStage 方法執行Stage的提交。
先來看一下ActiveJob的說明。
ActiveJob
類說明
A running job in the DAGScheduler. Jobs can be of two types: a result job, which computes a ResultStage to execute an action, or a map-stage job, which computes the map outputs for a ShuffleMapStage before any downstream stages are submitted. The latter is used for adaptive query planning, to look at map output statistics before submitting later stages. We distinguish between these two types of jobs using the finalStage field of this class. Jobs are only tracked for "leaf" stages that clients directly submitted, through DAGScheduler's submitJob or submitMapStage methods. However, either type of job may cause the execution of other earlier stages (for RDDs in the DAG it depends on), and multiple jobs may share some of these previous stages. These dependencies are managed inside DAGScheduler.
它代表了正執行在DAGScheduler中的一個job,job有兩種型別:result job,其通過計算一個ResultStage來執行一個action操作;map-stage job,它在下游的stage提交之前,為ShuffleMapStage計算map的輸出。
構造方法
finalStages是這個job的最後一個stage。
提交Stage前的準備
直接先來看submitStage方法,如下:
思路: 首先先獲取可能丟失的父stage資訊,如果該stage的父stage被遺漏了,則遞迴呼叫檢視其爺爺stage是否被遺漏。
查詢遺漏父Stage
getMissingParentStages方法如下:
思路:不斷建立父stage,可以看上篇文章 spark 原始碼分析之十九 -- DAG的生成和Stage的劃分 做進一步瞭解。
提交Stage
submitMissingTasks方法過於長,為方便分析,按功能大致分為如下部分:
獲取Stage需要計算的partition資訊
org.apache.spark.scheduler.ResultStage#findMissingPartitions 方法如下:
org.apache.spark.scheduler.ShuffleMapStage#findMissingPartitions 方法如下:
org.apache.spark.MapOutputTrackerMaster#findMissingPartitions 方法如下:
將stage和分割槽記錄到OutputCommitCoordinator中
OutputCommitCoordinator 的 stageStart實現如下:
本質上就是把它放入到一個map中了。
獲取分割槽的優先位置
思路:根據stage的RDD和分割槽id獲取到其rdd中的分割槽的優先位置。
下面看一下 getPreferredLocs 方法:
註釋中說到,它是執行緒安全的,下面看一下,它是如何實現的,即 getPrefferredLocsInternal 方法。
這個方法中提到四種情況:
1. 如果之前獲取到過,那麼直接返回Nil即可。
2. 如果之前已經快取在記憶體中,直接從快取的記憶體控制程式碼中取出返回即可。
3. 如果RDD對應的是HDFS輸入的檔案等,則使用RDD記錄的優先位置。
4. 如果上述三種情況都不滿足,且是narrowDependency,則呼叫該方法,獲取子RDDpartition對應的父RDD的partition的優先位置。
下面仔細說一下中間兩種情況。
從快取中取
getCacheLocs 方法如下:
思路:先檢視rdd的儲存級別,如果沒有儲存級別,則直接返回Nil,否則根據RDD和分割槽id組成BlockId集合,請求儲存系統中的BlockManager來獲取block的位置,然後轉換為TaskLocation資訊返回。
獲取RDD的優先位置
RDD的 preferredLocations 方法如下:
思路:先從checkpoint中找,如果checkpoint中沒有,則返回預設的為Nil。
返回物件是TaskLocation物件,做一下簡單的說明。
TaskLocation
類說明
A location where a task should run. This can either be a host or a (host, executorID) pair. In the latter case, we will prefer to launch the task on that executorID, but our next level of preference will be executors on the same host if this is not possible.
它有三個子類,如下:
這三個類定義如下:
很簡單,不做過多說明。
TaskLocation伴隨物件如下,現在用的方法是第二種 apply 方法:
建立新的StageInfo
對應方法如下:
org.apache.spark.scheduler.Stage#makeNewStageAttempt 方法如下:
很簡單,主要是呼叫了StageInfo的fromStage方法。
先來看Stage類。
StageInfo
StageInfo封裝了關於Stage的一些資訊,用於排程和SparkListener傳遞stage資訊。
其伴生物件如下:
廣播要執行task函式
對應原始碼如下:
通過broadcast機制,將資料廣播到spark叢集中的driver和各個executor中。關於broadcast的實現細節,可以檢視 spark 原始碼分析之十四 -- broadcast 是如何實現的?做進一步瞭解。
生成Task集合
根據stage的型別生成不同的型別Task。關於過多Task 的內容,在階段四進行剖析。
TaskScheduler提交TaskSet
對應程式碼如下:
其中taskScheduler是 TaskSchedulerImpl,它是TaskScheduler的唯一子類實現。它負責task的排程。
org.apache.spark.scheduler.TaskSchedulerImpl#submitTasks方法實現如下:
其中 createTaskSetManager 方法如下:
關於更多TaskSetManager的內容,將在階段四進行剖析。
backend是一個 SchedulerBackend 例項。在SparkContetx的初始化過程中呼叫 createTaskScheduler 初始化 backend,具體可以看 spark 原始碼分析之四 -- TaskScheduler的建立和啟動過程 做深入瞭解。
在yarn 模式下,它有兩個實現yarn-client 模式下的 org.apache.spark.scheduler.cluster.YarnClientSchedulerBackend實現 和 yarn-cluster 模式下的 org.apache.spark.scheduler.cluster.YarnClusterSchedulerBackend 實現。
這兩個類在spark 專案的 resource-managers 目錄下的 yarn 目錄下定義實現,當然它也支援 kubernetes 和 mesos,不做過多說明。
這兩個類的繼承關係如下:
org.apache.spark.scheduler.cluster.CoarseGrainedSchedulerBackend#reviveOffers 實現如下:
傳送ReviveOffers 請求給driver。
driver端的 CoarseGrainedSchedulerBackend 的 receive 方法有如下事件處理分支:
其內部經過一系列RPC過程,關於 RPC 可以看 spark 原始碼分析之十二--Spark RPC剖析之Spark RPC總結 做進一步瞭解。
即會呼叫driver端的makeOffsers方法,如下:
總結
本篇文章剖析了從DAGScheduler生成的Stage是如何被提交給TaskScheduler,以及TaskScheduler是如何把TaskSet提交給ResourceManager的。
下面就是task的執行部分了,下篇文章對其做詳細介紹。跟task執行關係很密切的TaskSchedulerBackend、Task等內容,也將在下篇文章做更詳細的說明。