Spark任務運行流程(基於yarn集羣模式)源碼分析(1)

博主博客地址:https://bryce-loski.github.io/

Spark任務運行流程(基於yarn集羣模式)源碼分析(1)

寫在前面的話
本文通過通俗易懂的方式,將以spark的yarn集羣模式,通過源碼層面去分析spark的任務調度流程。因爲源碼量巨大,所以只分析調度任務時所經歷的主要流程。
注:閱讀前需要具備一點點scala基礎

1.1 Spark核心組件

  • Driver
    Spark的驅動器節點,用於執行spark的main方法,負責實際代碼的執行工作。
    主要責任有:
  1. 將用戶程序轉化爲作業(job)
  2. 在Executor之間調度任務(Task)
  3. 跟蹤Executor的執行情況
  4. 通過UI展示查詢運行情況
  • Executor
    Spark Executor節點是負責在Spark作業中運行具體的任務,任務彼此之間互相獨立。Spark應用啓動時,Executor加點被同時啓動,並且始終伴隨着整個Spark用用的生命週期。
    Executor自動啓動HA機制,當某個節點運行故障,Spark應用會自動將出錯節點上的任務調度到其他的節點上繼續執行。
    Executor的核心功能:
  1. 負責運行組成Spark的應用任務,並將結果返回Driver端
  2. 它們通過自身的塊管理器(Block Manager)爲用戶程序中要求緩存的 RDD 提供內存式存儲。RDD是直接緩存在Executor進程內的,因此任務可以在運行時充分利用緩存數據加速運算。
  • Spark通用運行流程概述
    NuiIyT.png
    上圖體現spark基本的運行流程:
  1. 在任務提交以後,Saprk會先啓動Driver程序
  2. 隨後Driver分出兩條線,
    2.1 一條從集羣管理器去註冊應用程序,因爲有這條線,Spark才能與yarn結合。實現可插拔。
    2.2 另一條執行main函數。 Spark的查詢爲懶執行,當執行到Action算子時開始反向推算,根據寬依賴進行Stage的劃分,隨後每一個Stage對於一個Taskset。
  3. Task分發到執行的Executor去執行。

Spark在yarn的提交流程

準備工作

準備兩個依賴

        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-yarn_2.12</artifactId>
            <version>2.4.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-core_2.12</artifactId>
            <version>2.4.5</version>
        </dependency>

2.1 指令執行入口

在yarn運行模式下。spark會通過一個指令,向yarn提交任務。本文以官方案例,spark PI爲例:

bin/spark-submit \
--class org.apache.spark.examples.SparkPi \
--master yarn \
./examples/jars/spark-examples_2.12-2.4.5.jar \
10

從指令中可以看到,這個指令式執行spark-submit,傳入參數。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ESvzAT0e-1592550302362)(https://s1.ax1x.com/2020/06/19/NKFNqO.png)]
用文本文件打開這個文件,裏面只有不到30行代碼。主要帶代碼在最後一句。這個指令主要執行
文件bin目錄下的spark-class的org.apache.spark.deploy.SparkSubmit類

exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

打開spark-class可以找到
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-8BfAnhZ9-1592550302366)(https://s1.ax1x.com/2020/06/19/NKFcsf.png)]
從最後一行開始。這個文件執行了

  1. CMD參數。
  2. CMD參數用過while循環,將build_command封裝進CMD
  3. build_command配置了JVM的內存,前面的Runner封裝了java的配置。
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DZJgE3g2-1592550302370)(https://s1.ax1x.com/2020/06/19/NKFHyV.png)]
    所以通過拼接,最後得出的指令爲:
bin/java -Xmx128m -cp org.apache.spark.deploy.SparkSubmit

這條指令通過java的指定去啓動一個進程,這個進程爲spark-submit。

2.2 SparkSubmit

通過上面這條指令。可以得出,sparkSubmit這個類中一定有一個main方法作爲方法的入口,去執行。所以在idea中去尋找這個類,的伴生類對象。

override def main(args: Array[String]): Unit = {
    val submit = new SparkSubmit() {
      self =>

      override protected def parseArguments(args: Array[String]): SparkSubmitArguments = {
        new SparkSubmitArguments(args) {
          override protected def logInfo(msg: => String): Unit = self.logInfo(msg)

          override protected def logWarning(msg: => String): Unit = self.logWarning(msg)
        }
      }

      override protected def logInfo(msg: => String): Unit = printMessage(msg)

      override protected def logWarning(msg: => String): Unit = printMessage(s"Warning: $msg")

      override def doSubmit(args: Array[String]): Unit = {
        try {
          super.doSubmit(args)
        } catch {
          case e: SparkUserAppException =>
            exitFn(e.exitCode)
        }
      }

    }

    submit.doSubmit(args)
  }

進入main方法。可以看到main方法中前面所有的方法都是在聲明類信息。只有最後一行去調用了SparkSubmit的doSubmit方法,並將args作爲參數傳入。
目前的結構是:

SparkSubmit

    -- main
    
        // 執行提交
        // args表示應用程序的命令行參數
        -- submit.doSubmit(args)

2.2.1 doSubmit

點擊進入doSubmit
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-5qvyc5St-1592550302373)(https://s1.ax1x.com/2020/06/19/NKkPOK.png)]
代碼量很少,關鍵的代碼只有

val appArgs = parseArguments(args)

從字面意思,這句話對傳入的args進行參數的解析。

2.2.1.1 parseArguments

進入 parseArguments -> SparkSubmitArguments
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-m6bOHnJn-1592550302376)(https://s1.ax1x.com/2020/06/19/NKkMOf.png)]
可以看到很熟悉的東西。這個類將各種參數進行封裝,封裝成自己的類。
在下文可以看到處理的過程。但是這個並不是本文的主要內容所以略過。
NKkYfs.png
在本類中可以找到一個handle的方法:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jcbNCNAa-1592550302378)(https://s1.ax1x.com/2020/06/19/NKkUlq.png)]
這個方法通過模式匹配對命令行的參數進行了封裝。如:
–class org.apache.spark.examples.SparkPi
–master yarn
將 org.apache.spark.examples.SparkPi => master
將yarn -> master
所以目前的結構是:

SparkSubmit

    -- main
    
        // 執行提交
        // args表示應用程序的命令行參數
        -- submit.doSubmit(args)
        
        // 解析命令行參數
        // --master : yarn            => master
        // --class  : xxxxx.WordCount => mainClass
        -- parseArguments

sparkSubmit執行main -> dosubmit -> parseArguments ->** new SparkSubmitArguments** -> 通過handle方法,將命令行的各個參數傳入到了spark程序中。

2.2.1.2 action

返回doSubmit層,往下看可以看到一個action方法:

    appArgs.action match {
      case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)
      case SparkSubmitAction.KILL => kill(appArgs)
      case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
      case SparkSubmitAction.PRINT_VERSION => printVersion()
    }

通過代碼可以得知appargs通過模式匹配執行了action,由於沒有case _的選項,所以action一定在某個地方被賦值了。
點擊進入action所在的SparkSubmitArguments,可以找到這樣一行代碼:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xjJoImn4-1592550302379)(https://s1.ax1x.com/2020/06/19/NKkB0U.png)]
通過一定的scala基礎,可以分析出action在這裏被賦值上了SUBMIT
最後action走的是

case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog)

這一行代碼。

2.2.2 submit(appArgs, uninitLog)

進入action的submit函數
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BbLnO6ib-1592550302380)(https://s1.ax1x.com/2020/06/19/NKk40O.png)]
裏面有一個doRunMain函數,由於沒有調用這個函數,所以這個函數不被執行,先跳過,看下面的if-else方法

  if (args.isStandaloneCluster && args.useRest) {
      try {
        logInfo("Running Spark using the REST application submission protocol.")
        doRunMain()
      } catch {
        // Fail over to use the legacy submission gateway
        case e: SubmitRestConnectionException =>
          logWarning(s"Master endpoint ${args.master} was not a REST server. " +
            "Falling back to legacy submission gateway instead.")
          args.useRest = false
          submit(args, false)
      }
    // In all other modes, just run the main class as prepared
    } else {
      doRunMain()
    }

由於本文是基於yarn部署環境。所以直接查看else中的doRunMain方法

2.2.2.1 doRunMain

doRunMain方法中,if函數判斷是否有代理用戶,當前執行命令並沒有設定,所以執行else中的runMain方法

2.2.3 runMain

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YgKuMfhr-1592550302381)(https://s1.ax1x.com/2020/06/19/NKkb9A.png)]
這個函數中開始準備做提交的內容。

val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)

這個函數關鍵信息也在第一句,準備提交環境。

加下來:

//設定類加載器。
 Thread.currentThread.setContextClassLoader(loader)
//加載和獲取指定名稱的類信息
 mainClass = Utils.classForName(childMainClass)
//動態(反射)創建對象
 mainClass.newInstance().asInstanceOf[SparkApplication]
 //同時在之後的代碼調用start方法
 app.start(childArgs.toArray, sparkConf)

小結回顧
進入start方法,會發現start是一個抽象類。
所以,代碼一定在之前就已經被實現。
回顧app方法,是從mainClass.newInstance().asInstanceOf[SparkApplication]這個方法過來,
mainClass是從
mainClass = Utils.classForName(childMainClass)得到的類信息
childMainClass是
val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)準備提交環境的時候返回的結果。
所以以上的代碼就連成了串,關鍵在於
prepareSubmitEnvironment

2.2.4 prepareSubmitEnvironment

  1. 進入 prepareSubmitEnvironment方法
    NKk7hd.png
    這是一個代碼量非常大的函數
  2. 直接看末尾處,**748行處(childArgs, childClasspath, sparkConf, childMainClass)**這是函數的返回結果,從返回結果往上推,能找到賦值的地方。
  3. 查找childMainClass跟着提示往上走,會找到這樣一個代碼段
    NKAi3n.png
 childMainClass = YARN_CLUSTER_SUBMIT_CLASS

這裏是Yarn的cluster模式,進入YARN_CLUSTER_SUBMIT_CLASS可以看到完整的類對象爲org.apache.spark.deploy.yarn.YarnClusterApplication

  1. 繼續查找會看到client模式的代碼
    NKAP9s.png
childMainClass = args.mainClass

而mainClass在前面已經提到爲–class的值
所以目前的結構是:

SparkSubmit

    -- main
    
        // 執行提交
        // args表示應用程序的命令行參數
        -- submit.doSubmit(args)
        
        // 解析命令行參數
        // --master : yarn            => master
        // --class  : xxxxx.WordCount => mainClass
        -- parseArguments
        
        // 
        -- submit
            // 執行主程序
            -- doRunMain
            
                // 執行主程序
                -- runMain
                
                    // childArgs
                    // childClasspath
                    // sparkConf
                    // childMainClass
                    // 準備提交的環境
                    -- prepareSubmitEnvironment(args)
                    
                        // client        => xxxxx.WordCount
                        // isYarnCluster => org.apache.spark.deploy.yarn.YarnClusterApplication
                        -- (childArgs, childClasspath, sparkConf, childMainClass)
                    
                    // 設定類加載器
                    -- Thread.currentThread.setContextClassLoader(loader)
                    
                    // 加載和獲取指定名稱的類信息
                    -- mainClass = Utils.classForName(childMainClass)
                    
                    // 動態(反射)創建對象
                    -- app = mainClass.newInstance().asInstanceOf[SparkApplication]
                    
                    -- app.start

到此SparkSubmit就已經結束

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