寫在前面的話:本篇博客爲原創,認真閱讀需要比對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通信的請先自行百度,之後我補上後附博客地址)
- 找到該線程的執行入口,main方法,發現主要是對參數的賦值
- 往下滑,找到了其中的run方法,點進去,我們看看是怎麼執行的
- 既然這個類爲EndPoint,所以它也要構建環境
- 同時作爲EndPoint,還需要把自己設置爲節點,也就是EndPoint生命週期中的constructor
- 按照生命週期,接下來該運行onStart()方法,通過ctrl + F,找到這個方法,同時還向Driver發送註冊消息
- 因爲使用的是ask,所以應該由CoarseGrainedSchedulerBackend類中的receiveAndReply()方法來進行接收
- 如果可以註冊,就返回true,併發送消息,由CoarseGrainedExecutorBackend類的receive()方法接收
- 收到上一步發送的消息,創建Excutor
- 從第7步中的主線makeOffsets()方法進入,啓動任務
- 先執行括號內的scheduler.resourceOffers(workOffers),主要是對task進行了排序
- 第10步執行完括號內部的再返回來執行launchTasks,在裏面發送消息
- 這個消息肯定是由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()
- 先double shift到RDD中,在ctrl + F 拿到collect(),執行其中的runJob方法
- 經過3次runJob的調用後(中間省略了3次點擊runJob),終於到了dagScheduler的調用上(DAG爲有向無環圖)
- 在這個runJob中,終於找到了提交Job的函數
- 在這個方法中,給自己發送一個提交任務的作業
- 那麼有post給自己發送,就有receive自己接收,所以我們ctrl + F 搜索receive()方法,果然有一個方法來專門處理收到的event
- 進去這個方法,匹配到了Job提交,所以執行dagScheduler.handleJobSubmitted
- 怎麼處理呢?先創建最終階段
- 讓我們看下創建的過程,在這裏面獲取到了最終的父階段,還拿到了所有的shuffle依賴
- 現在已經獲取到了最終階段的stage,我們返回到第7步這個位置,滑倒這個handle方法的最下面,找到了提交任務的最終階段
- 那我們來看一下它怎麼提交最終階段,但是卻實際上時先提交的第一階段呢?獲取最後一個stage的上一個stage,然後判斷上一個stage是否爲空,如果不爲空,就繼續遞歸調用該方法,直到沒有上一個stage,也就是到達第一個stage時,開始執行submitMissingTasks(stage, jobId.get)
- 接下來我們進入submitMissingTasks,匹配stage,獲取到最優先的資源位置來運行job
- 再往下滑,定義了這個tasks,並且根據stage的依賴情況賦值
val tasks = partitionsToCompute.map{new ShuffleMapTask}
val tasks = partitionsToCompute.map{new ResultTask}
- 再往下滑,把任務由DAGScheduler交由TaskScheduler處理
以下就是在TaskScheduler類中處理了
- 從一步的方法進來,發現這個方法是抽象的,我們找實現類
ctrl + h,找到實現類TaskSchedulerImpl - 把tasks封裝成TaskSetManager,並且放入調度池中
- 然後往下滑一下,執行:backend.reviveOffers(),從這個方法進入,ctrl + h 獲取實現類CoarseGrainedSchedulerBackend,找到reviveOffers
在該方法中,執行launchTasks(scheduler.resourceOffers(workOffers))
好了,到此任務切分的源碼也分析完了,再返回來這張流程圖,所有的步驟都已經分析完了,合起來也就是 Excutor端向Driver端註冊後,Driver端把切分好的任務按照位置最優策略分配給Excutor。
下一次我們繼續給大家詳細分析上面的第15步,TaskSetManager如何放入調度池中,並且瞭解調度池的區別,以及在調度池中如何進行排序。
最後,我知道自己表達的知識點還很欠缺,歡迎大家指正與討論,歡迎關注我的公衆號:後來X,回覆:spark源碼,獲取spark2.1.1源碼包
持續更新,未完待續!