spark核心(下)——job任務提交原始碼解析
Driver ---->main ----> SparkContext ---> RDD ---> RDD ---->RDD
---RDD.collect()
第一部分: SparkContext的構造
SparkContext (Driver構造)
核心屬性: var _env: SparkEnv : 封裝了Spark所有的環境資訊(cache,序列化器,BM)
_env = createSparkEnv(_conf, isLocal, listenerBus)
var _taskScheduler: TaskScheduler //任務排程器,目的將Task傳送給Executor執行
val (sched, ts) = SparkContext.createTaskScheduler(this, master, deployMode) //528行
--case masterUrl =>
--val scheduler = cm.createTaskScheduler(sc, masterUrl) // YARN Cluster模式
-- val backend = cm.createSchedulerBackend(sc, masterUrl, scheduler) //YarnClusterSchedulerBackend
--YarnClusterSchedulerBackend 父類 YarnSchedulerBackend 的構造器中
-- 爺爺類 CoarseGrainedSchedulerBackend 的構造器
// 名稱: CoarseGrainedScheduler 端點型別: DriverEndpoint
--屬性 val driverEndpoint = rpcEnv.setupEndpoint(ENDPOINT_NAME, createDriverEndpoint())
--cm.initialize(scheduler, backend) // 初始化排程器要使用的排程池
--scheduler.asInstanceOf[TaskSchedulerImpl].initialize(backend)
--initialize //TaskSchedulerImpl.initialize
//根據配置的排程模式,來建立對應的排程池,排程模式取決於配置spark.scheduler.mode,預設為FIFO
-- schedulableBuilder =new FIFOSchedulableBuilder(rootPool)
--schedulableBuilder.buildPools()
-- (backend, scheduler)
private var _dagScheduler: DAGScheduler //將RDD根據依賴劃分階段,在WebUI上可以檢視DAG圖,DAG是Spark區分於Hadoop的根本特徵
--val eventProcessLoop = new DAGSchedulerEventProcessLoop(this)
--eventProcessLoop.start() //在構造DAGScheduler時就執行
-- onStart() //{}
--eventThread.start()
----eventThread.run(){
while (!stopped.get) {
val event = eventQueue.take() //從事件佇列中取出一個事件
try {
onReceive(event) //處理事件
--doOnReceive(event)
--case JobSubmitted
=>dagScheduler.handleJobSubmitted // 見第三部分
}
}
第二部分: 呼叫行動運算元,提交Job
RDD.collect
--val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
-- dagScheduler.runJob(rdd, cleanedFunc, partitions, callSite, resultHandler, localProperties.get)
--val waiter = submitJob(rdd, func, partitions, callSite, resultHandler, properties) // DAGScheduler 提交Job
--val jobId = nextJobId.getAndIncrement() //生成JobID
// 要求分割槽不能為空,如果分割槽為空,就立刻退出,如果有分割槽,此時
--eventProcessLoop.post(JobSubmitted) // 向 事件處理迴圈中 提交一個事件,將事件提交到佇列的尾部,之後會有執行緒自動處理事件
--eventQueue.put(event)
第三部分: DAGScheduler.handleJobSubmitted 處理提交的Job
--dagScheduler.handleJobSubmitted
--var finalStage: ResultStage = null
--finalStage = createResultStage(finalRDD, func, partitions, jobId, callSite)
--val parents = getOrCreateParentStages(rdd, jobId) // 獲取當前階段的父階段(不是祖先階段)
// 獲取當前RDD直接的有shuffle依賴的父親
-- getShuffleDependencies(rdd).map { shuffleDep =>
getOrCreateShuffleMapStage(shuffleDep, firstJobId) // 一旦查詢父RDD的shuffle依賴資訊,此時就建立一個ShuffleMapStage
// 先從之前建立的Map集合中取
--shuffleIdToMapStage.get(shuffleDep.shuffleId) match {
case Some(stage) =>
stage //取出就返回
case None => //沒取到
// 將當前ShuffleMapStage 的所有缺失的 祖先ShuffleMapStage全部建立
getMissingAncestorShuffleDependencies(shuffleDep.rdd).foreach { dep =>
// 判斷祖先 ShuffleMapStage是否確實
if (!shuffleIdToMapStage.contains(dep.shuffleId)) {
// 先建立 祖先 ShuffleMapStage
createShuffleMapStage(dep, firstJobId)
}
}
// Finally, create a stage for the given shuffle dependency.
// 最後,再建立當前 rdd 直接 父RDD的ShuffleMapStage
createShuffleMapStage(shuffleDep, firstJobId)
-- val numTasks = rdd.partitions.length //numTasks=分割槽數
--val parents = getOrCreateParentStages(rdd, jobId)
--val stage = new ShuffleMapStage
--shuffleIdToMapStage(shuffleDep.shuffleId) = stage
// 所有的ShuffleMapStage全部結束
}
}.toList
-- val stage = new ResultStage(id, rdd, func, partitions, parents, jobId, callSite)
// 所有的Stage全部結束, Stage 要麼是 ShuffleMapStage,要麼是 ResultStage,最後一個是ResultStage
// 如果是ResultStage,提交result-job,如果是ShuffleMapStage,執行 map-shuffle job
-- val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)
-- finalStage.setActiveJob(job)
--submitStage(finalStage) // 提交最後的階段,DAGScheduler完成工作
// 獲取當前最後階段是否有沒有提交的缺失的父階段,將缺失的父階段按照ID升序排序,ID小的會被優先提交
--val missing = getMissingParentStages(stage).sortBy(_.id)
--if (missing.isEmpty){ //沒有缺失的父階段,都已經提交或壓根就沒有父階段
--submitMissingTasks(stage, jobId.get) //提交當前階段,封裝Task來執行這個階段的邏輯
--val tasks: Seq[Task[_]]=try{
case stage: ShuffleMapStage =>
partitionsToCompute.map{
new ShuffleMapTask //如果是ShuffleMapStage,每個分割槽都建立一個 ShuffleMapTask
}
case stage: ResultStage =>
partitionsToCompute.map{
new ResultTask //如果是ShuffleMapStage,每個分割槽都建立一個 ResultTask
}
}
--if (tasks.nonEmpty){
taskScheduler.submitTasks(new TaskSet) // 一個Stage如果有多個分割槽,就有多個Task,這些Task會放入一個TaskSet
// 一個stage對應一個 TaskSet
}
}else {
for (parent <- missing) {
submitStage(parent) // 只要有沒有提交的父階段,就先提交父階段
}
waitingStages += stage // 將當前提交的父階段放入要等待執行的階段中
第四部分: TaskScheduler 負責排程 Task 傳送到Executor進行計算
TaskScheduler
--submitTasks(new TaskSet)
--val tasks = taskSet.tasks // 從TaskSet中取出 tasks
--val manager = createTaskSetManager(taskSet, maxTaskFailures)
// 每個Stage 會建立一個 TaskSet,每個TaskSet又會建立一個對應的TaskSetManager
--new TaskSetManager(this, taskSet, maxTaskFailures, blacklistTrackerOpt)
--val stage = taskSet.stageId // 獲取當前階段ID
--schedulableBuilder.addTaskSetManager(manager, manager.taskSet.properties) // 將當前TaskSetManager新增到排程池中
--backend.reviveOffers() // 從排程池中,取出TaskSetManager,開始排程
-- driverEndpoint.send(ReviveOffers) //CoarseGrainedSchedulerBackend.reviveOffers
第五部分 : DriverEndpoint 處理 ReviveOffers訊息
DriverEndpoint
--receive
-- case ReviveOffers => makeOffers()
--val taskDescs={
--val activeExecutors = executorDataMap.filterKeys(isExecutorActive) // 篩選當前active狀態的所有的executors
--val workOffers = activeExecutors.map{
case (id, executorData) => new WorkerOffer // 每個active的executor都建立一個 WorkerOffer
}
//此方法由叢集呼叫,在叢集的slave階段上準備資源。 根據TaskSet的優先順序,以輪詢的方式傳送到叢集,以保證負載均衡
--scheduler.resourceOffers(workOffers)
--val sortedTaskSets = rootPool.getSortedTaskSetQueue.filterNot(_.isZombie)
--val sortedTaskSetQueue = new ArrayBuffer[TaskSetManager] // 建立一個排序後的TaskSetManager的集合
// 從rootPool中獲取schedulableQueue,之後將佇列中的TaskSet,呼叫 排程演算法 進行排序
// 預設使用 FIFOSchedulingAlgorithm() 的比較器排序,stageId 小的排在前面
--val sortedSchedulableQueue =
schedulableQueue.asScala.toSeq.sortWith(taskSetSchedulingAlgorithm.comparator)
// 將排好序的 TaskSetManager取出,依次放入sortedTaskSetQueue
--sortedTaskSetQueue
// 對已經排好序的TaskSetManager 進行各種處理(看哪個executor適合運算這個TaskSetManager)
// 每個TaskSetManager 都有一個屬性 var myLocalityLevels = computeValidLocalityLevels()
// 在計算時,import TaskLocality.{PROCESS_LOCAL, NODE_LOCAL, NO_PREF, RACK_LOCAL, ANY}
// 所有的 TaskSetManager的myLocalityLevels 都有ANY,之後看是否複合其他條件,加入對應的級別
-- for (currentMaxLocality <- taskSet.myLocalityLevels) { //嘗試從TaskSetManager的所有本地化級別中獲取最優的
-- return tasks // 返回設定和處理後的tasks
}
--if (taskDescs.nonEmpty) {
launchTasks(taskDescs) // 傳送Task
--val serializedTask = TaskDescription.encode(task) //序列化每個Task的描述
-- val executorData = executorDataMap(task.executorId) // 獲取當前Tasks應該發往的executor的資訊
// 獲取要傳送的exeuctor的通訊端點的引用,傳送 LaunchTask資訊,同時將 TaskDesc也傳送過去
-- executorData.executorEndpoint.send(LaunchTask(new SerializableBuffer(serializedTask)))
}
第六部分: 通訊中的Executor收到訊息進行處理
CoarseGrainedExecutorBackend.receive
-- case LaunchTask(data) =>
--val taskDesc = TaskDescription.decode(data.value) // 將TaskDesc反序列化
--executor.launchTask(this, taskDesc)
--val tr = new TaskRunner(context, taskDescription) // 建立一個執行Task的執行緒
--threadPool.execute(tr) // 執行執行緒,執行tr的run方法
// 從Task的描述資訊中,反序列化取出Task
-- task = ser.deserialize[Task[Any]](
taskDescription.serializedTask, Thread.currentThread.getContextClassLoader)
--task.run
-- val taskContext = new TaskContextImpl
--runTask(context)
//不同型別的Task執行自己的處理邏輯
// 將當前stage的結果寫出
-- ShuffleMapTask
--dep.shuffleWriterProcessor.write(rdd, dep, mapId, context, partition)
-- var writer: ShuffleWriter[Any, Any] = null
-- val manager = SparkEnv.get.shuffleManager
// shuffle以什麼形式寫出,由shuffleManager決定
--writer = manager.getWriter
// 使用RDD的迭代器,寫出當前Stage最後一個RDD中的資料
--writer.write(
// rdd.iterator 是個方法
rdd.iterator(partition, context).asInstanceOf[Iterator[_ <: Product2[Any, Any]]])
// 判斷是否使用了快取
-- if (storageLevel != StorageLevel.NONE) {
// 從快取中獲取RDD,如果快取中沒有就計算得到
getOrCompute(split, context)
} else {
// 嘗試從ck目錄中獲取RDD,如果沒有就計算得到
computeOrReadCheckpoint(split, context)
--if (isCheckpointedAndMaterialized) {
// 如果ck目錄有,獲取CheckPointRDD,呼叫它的iterator方法
firstParent[T].iterator(split, context)
} else {
//計算得到
compute(split, context)
-- textFile new HadoopRDD : compute的邏輯就是返回迭代當前這片資料的迭代器
-- map MapPartitionsRDD : compute
f(context, split.index, firstParent[T].iterator(split, context))
--(_, _, iter) => iter.map(cleanF) (context, split.index, firstParent[T].iterator(split, context))
-- (_, _, iter) => iter.map( word => (word, 1)) (context, split.index, firstParent[T].iterator(split, context))
}
}
// 執行行動運算元最終的邏輯
-- ResultTask
// func 是呼叫行動運算元的RDD 行動運算元的邏輯
// 以collect 為例,func: (iter: Iterator[T]) => iter.toArray
// rdd.iterator(partition, context) : 呼叫 ResultStage 最後一個呼叫行動運算元的RDD的compute()
//
--func(context, rdd.iterator(partition, context))
相關文章
- spark原始碼之任務提交過程Spark原始碼
- Spark3.0YarnCluster模式任務提交流程原始碼分析SparkYarn模式原始碼
- [原始碼解析]Oozie來龍去脈之提交任務原始碼
- Spark 原始碼解析 : DAGScheduler中的DAG劃分與提交Spark原始碼
- Docker中提交任務到Spark叢集DockerSpark
- MapReduce——客戶端提交任務原始碼分析客戶端原始碼
- Flink原始碼剖析:Jar包任務提交流程原始碼JAR
- Mapreduce Job提交流程原始碼和切片原始碼詳解原始碼
- spark reduceByKey原始碼解析Spark原始碼
- spark核心原始碼深度剖析Spark原始碼
- LiteOS核心原始碼分析:任務LOS_Schedule原始碼
- spark 原始碼分析之十九 -- Stage的提交Spark原始碼
- spark-submit提交任務時執行流程(簡單版)SparkMIT
- Flink 原始碼解析--Stream、Job、ExecutionGraph的生成示例原始碼
- Spark原始碼解析-Yarn部署流程(ApplicationMaster)Spark原始碼YarnAPPAST
- Python程式碼解析: job = next(job for job in jobs if job.job_id == job_id)Python
- YARN原始碼解析(3)-作業提交2Yarn原始碼
- Spark Shuffle機制詳細原始碼解析Spark原始碼
- Spring原始碼系列:核心概念解析Spring原始碼
- Oracle 任務管理之 ----program(程式)---scheduler(計劃)--Job(任務)Oracle
- [原始碼解析] 分散式任務佇列 Celery 之啟動 Consumer原始碼分散式佇列
- job任務均不執行,手工執行報job now running
- Spark 原始碼系列(六)Shuffle 的過程解析Spark原始碼
- Laravel核心解讀–Session原始碼解析LaravelSession原始碼
- webpack核心模組tapable原始碼解析Web原始碼
- 【高併發】通過ThreadPoolExecutor類的原始碼深度解析執行緒池執行任務的核心流程thread原始碼執行緒
- Spark SQL原始碼解析(四)Optimization和Physical Planning階段解析SparkSQL原始碼
- xxl-job原始碼閱讀二(服務端)原始碼服務端
- Serverless JOB | 傳統任務新變革Server
- Serverless JOB——傳統任務新變革Server
- 圖解Spark排序運算元sortBy的核心原始碼圖解Spark排序原始碼
- spark的基本運算元使用和原始碼解析Spark原始碼
- Spring 事務原始碼解析Spring原始碼
- 懸賞任務系統原始碼_任務接單平臺原始碼PHP版附app原始碼PHPAPP
- MySQL核心原始碼解讀-SQL解析一MySql原始碼
- dva-原始碼解析-下原始碼
- Spark 原始碼系列(九)Spark SQL 初體驗之解析過程詳解Spark原始碼SQL
- LiteOS-任務篇-原始碼分析-任務排程函式原始碼函式