大數據Spark YarnCluster模式源碼分析——提交任務2+切分任務(手把手看源碼)

寫在前面的話:本篇博客爲原創,認真閱讀需要比對spark 2.1.1的源碼,預計閱讀耗時30分鐘,如果大家發現有問題或者是不懂的,歡迎討論
歡迎關注公衆號:後來X

spark 2.1.1的源碼包(有需要自取):關注公衆號【後來X】,回覆spark源碼
上一篇博文,我們看了在Yarn Cluster模式下,從Spark-submit提交任務開始,到最後啓動了ExecutorBackend線程,也就是進行到了圖中的第9步。
上一篇博文地址:https://blog.csdn.net/weixin_38586230/article/details/104342440
在這裏插入圖片描述

1、接下來先看Excutor端向Driver註冊

那麼今天接着看ExecutorBackend進程做了什麼,上次最後一步爲startContainer,但是實際的命令爲:
/bin/java org.apache.spark.executor.CoarseGrainedExecutorBackend
所以首先double shift,找到org.apache.spark.executor.CoarseGrainedExecutorBackend,
在這裏插入圖片描述
我們發現這個類繼承了extends ThreadSafeRpcEndpoint,所以說這個類也是一個Endpoint(RPC通信中的重要成員,不瞭解RPC通信的請先自行百度,之後我補上後附博客地址)

  1. 找到該線程的執行入口,main方法,發現主要是對參數的賦值
    在這裏插入圖片描述
  2. 往下滑,找到了其中的run方法,點進去,我們看看是怎麼執行的
    在這裏插入圖片描述
  3. 既然這個類爲EndPoint,所以它也要構建環境
    在這裏插入圖片描述
  4. 同時作爲EndPoint,還需要把自己設置爲節點,也就是EndPoint生命週期中的constructor
    在這裏插入圖片描述
  5. 按照生命週期,接下來該運行onStart()方法,通過ctrl + F,找到這個方法,同時還向Driver發送註冊消息
    在這裏插入圖片描述
  6. 因爲使用的是ask,所以應該由CoarseGrainedSchedulerBackend類中的receiveAndReply()方法來進行接收
    在這裏插入圖片描述
  7. 如果可以註冊,就返回true,併發送消息,由CoarseGrainedExecutorBackend類的receive()方法接收
    在這裏插入圖片描述
  8. 收到上一步發送的消息,創建Excutor
    在這裏插入圖片描述
  9. 從第7步中的主線makeOffsets()方法進入,啓動任務
    在這裏插入圖片描述
  10. 先執行括號內的scheduler.resourceOffers(workOffers),主要是對task進行了排序
    在這裏插入圖片描述
  11. 第10步執行完括號內部的再返回來執行launchTasks,在裏面發送消息
    在這裏插入圖片描述
  12. 這個消息肯定是由CoarseGrainedSchedulerBackend類中的receive方法進行接收,匹配到任務的執行。
    在這裏插入圖片描述
    上面這段過程進行了excutor端向Driver端進行註冊,註冊成功後,Driver端向excutord端發送任務,excutor端進行執行

那麼Driver端既然要向excutor端發送任務,就得先進行任務的切分,下面我們來分析Task任務的劃分的源碼分析

2、Task 任務的切分

說到任務的劃分,就不得不提到RDD,我們知道在spark中,算子只有在遇到行動算子纔會執行(如collect()),轉換算子都是懶加載,所以要想知道Task任務怎麼劃分的,得先從行動算子看起,我們下面以WordCount項目爲例:
word Count的Jar包的代碼如下:

dataRDD.flatMap(_.split(" ")).map((_,1)).reduceByKey(_ + _).collect()
  1. 先double shift到RDD中,在ctrl + F 拿到collect(),執行其中的runJob方法
    在這裏插入圖片描述
  2. 經過3次runJob的調用後(中間省略了3次點擊runJob),終於到了dagScheduler的調用上(DAG爲有向無環圖)
    在這裏插入圖片描述
  3. 在這個runJob中,終於找到了提交Job的函數
    在這裏插入圖片描述
  4. 在這個方法中,給自己發送一個提交任務的作業
    在這裏插入圖片描述
  5. 那麼有post給自己發送,就有receive自己接收,所以我們ctrl + F 搜索receive()方法,果然有一個方法來專門處理收到的event
    在這裏插入圖片描述
  6. 進去這個方法,匹配到了Job提交,所以執行dagScheduler.handleJobSubmitted
    在這裏插入圖片描述
  7. 怎麼處理呢?先創建最終階段
    在這裏插入圖片描述
  8. 讓我們看下創建的過程,在這裏面獲取到了最終的父階段,還拿到了所有的shuffle依賴
    在這裏插入圖片描述
  9. 現在已經獲取到了最終階段的stage,我們返回到第7步這個位置,滑倒這個handle方法的最下面,找到了提交任務的最終階段
    在這裏插入圖片描述
  10. 那我們來看一下它怎麼提交最終階段,但是卻實際上時先提交的第一階段呢?獲取最後一個stage的上一個stage,然後判斷上一個stage是否爲空,如果不爲空,就繼續遞歸調用該方法,直到沒有上一個stage,也就是到達第一個stage時,開始執行submitMissingTasks(stage, jobId.get)
    在這裏插入圖片描述
  11. 接下來我們進入submitMissingTasks,匹配stage,獲取到最優先的資源位置來運行job
    在這裏插入圖片描述
    在這裏插入圖片描述
  12. 再往下滑,定義了這個tasks,並且根據stage的依賴情況賦值
    val tasks = partitionsToCompute.map{new ShuffleMapTask}
    val tasks = partitionsToCompute.map{new ResultTask}
    在這裏插入圖片描述
  13. 再往下滑,把任務由DAGScheduler交由TaskScheduler處理
    在這裏插入圖片描述

以下就是在TaskScheduler類中處理了

  1. 從一步的方法進來,發現這個方法是抽象的,我們找實現類
    在這裏插入圖片描述
    ctrl + h,找到實現類TaskSchedulerImpl
  2. 把tasks封裝成TaskSetManager,並且放入調度池中
    在這裏插入圖片描述
  3. 然後往下滑一下,執行:backend.reviveOffers(),從這個方法進入,ctrl + h 獲取實現類CoarseGrainedSchedulerBackend,找到reviveOffers
    在這裏插入圖片描述
    在這裏插入圖片描述
    在該方法中,執行launchTasks(scheduler.resourceOffers(workOffers))
    在這裏插入圖片描述
    好了,到此任務切分的源碼也分析完了,再返回來這張流程圖,所有的步驟都已經分析完了,合起來也就是 Excutor端向Driver端註冊後,Driver端把切分好的任務按照位置最優策略分配給Excutor。

下一次我們繼續給大家詳細分析上面的第15步,TaskSetManager如何放入調度池中,並且瞭解調度池的區別,以及在調度池中如何進行排序。

最後,我知道自己表達的知識點還很欠缺,歡迎大家指正與討論,歡迎關注我的公衆號:後來X,回覆:spark源碼,獲取spark2.1.1源碼包

持續更新,未完待續!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章