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就已经结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值