Yarn Cluster模式下 spark提交流程和源码解析

本文深入剖析了Spark在YARN集群模式下的工作流程,从SparkSubmit开始,详细讲解了如何通过YarnClusterApplication提交应用,如何启动ApplicationMaster和Driver线程,以及Executor的注册和任务执行过程。源码分析涵盖了SparkSubmit、YarnClient、ApplicationMaster、ExecutorBackend等关键组件,展示了Spark与YARN交互的详细步骤。

这里是Yarn的Cluster模式,还有Yarn的Client模式以及StandAlone的Cluster和Client模式,这里先看最经典的;

Yarn-Cluster模式:

Cluster 模式将用于监控和调度的 Driver 模块启动在 Yarn 集群资源中执行。一般应用于实际生产环境。
   
1) 执行脚本提交任务,实际是启动一个 SparkSubmit 的 JVM 进程;
2) SparkSubmit 类中的 main 方法反射调用 YarnClusterApplication 的 main 方法;
3) YarnClusterApplication 创建 Yarn 客户端(Client类中封装的yarnClient对象),然后向 RM发送执行指令:bin/java ApplicationMaster4)随后 ResourceManager 分配 container,在合适的 NodeManager 上启动 ApplicationMaster
5ApplicationMaster 启动 Driver 线程,执行用户的作业;
6) AM 向 RM 注册,申请资源;
7) 获取资源后 AM 向 NM 发送指令:bin/java YarnCoarseGrainedExecutorBackend;启动ExecutorBackend8Executor启动后会向Driver进行反向注册,实例化Executor对象,等待任务;    
9Executor全部注册完成后Driver开始执行main函数;之后执行到 Action 算子时,触发一个 Job,并根据宽依赖开始划分 stage,每个 stage 生成对应的 TaskSet,之后将 task 分发到各个 Executor 上执行。

在这里插入图片描述

源码解析:

源码分析

1.1、SparkSubmit起点

  def main(args: Array[String]): Unit = {
   
   
    val submit = new SparkSubmit()
    submit.doSubmit(args)
  }

Spark-submit是一个提交程序;在main方法中val submit = new SparkSubmit()类;

submit.doSubmit(args) ; args为命令行参数;比如–master、–class等;

那么这些参数是如何被使用的呢?

doSubmit中有一个步骤appArgs = parseArguments(args);

parseArguments方法中只有一步,就是构建一个SparkSubmitArguments.Scala对象,也即上图中的橙色框内容;

SparkSubmitArguments对象中有一个方法parse(args.asJava),也即这个对象会把传入的参数进行解析,解析方式为正则表达式;会把传入的args拆分成opt和value两部分;也即指令和内容两部分,然后进行一个匹配操作(handle方法),完成操作的解析;

在appArgs = parseArguments(args)这一步下还有一个步骤:

	val appArgs = parseArguments(args)
    if (appArgs.verbose) {
   
   
      logInfo(appArgs.toString)
    }
    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()
    }

这里的action是从哪里得到的呢?

还是在SparkSubmitArguments.Scala类中有以下这么一个操作:

action = Option(action).getOrElse(SUBMIT) //第一次提交的时候,这里会返回一个Submit,也即appArgs.action = Submit;
    //上面那一步自然会执行case SparkSubmitAction.SUBMIT => submit(appArgs, uninitLog) //开始提交;
    //此时,这里的submit方法中会先做一个判断
    if (args.isStandaloneCluster && args.useRest) {
   
   
        ......
    }
    else {
   
   
      doRunMain()
    }

//其中doRunMain()方法也有一个判断:
if (args.proxyUser != null) {
   
    //命令行参数是否有代理用户;这里是没有传入的;
    
}
else {
   
   
        runMain(args, uninitLog)
}       

//进入runMain方法中:
 private def runMain(args: SparkSubmitArguments, uninitLog: Boolean): Unit = {
   
   
    val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args) //注意,这里的 childMainClass非常重要;
    // Let the main class re-initialize the logging system once it starts.
    if (uninitLog) {
   
   
      Logging.uninitialize()
    }
     //val (childArgs, childClasspath, sparkConf, childMainClass) = prepareSubmitEnvironment(args)非常重要
     

1.2、向Yarn提交应用

 val loader = getSubmitClassLoader(sparkConf)
    for (jar <- childClasspath) {
   
   
      addJarToClasspath(jar, loader)
    }

    var mainClass: Class[_] = null

    try {
   
   
      mainClass = Utils.classForName(childMainClass) //反射机制;通过childMainClass类名获取类的信息;
    } catch {
   
   
      ......
    }

    val app: SparkApplication = if (classOf[SparkApplication].isAssignableFrom(mainClass)) {
   
   
      mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication] //判断mainClass是否继承至SparkApplication类,如果继承了,则直接通过mainClass的构造器创建实例并转化为SparkApplication类;如果没有继承,则创建一个JavaMainApplication(mainClass)对象;          
    } else {
   
   
      new JavaMainApplication(mainClass)
    }

......
    
    try {
   
   
      app.start(childArgs.toArray, sparkConf) //不论是哪种结果,这里都会start对应的application;
    } catch {
   
   
      case t: Throwable =>
        throw findCause(t)
    }

//接下来分析childMainClass到底是什么;(实际上是之前提到的prepareSubmitEnvironment(args)方法的返回值)

接下来分析prepareSubmitEnvironment方法:

//prepareSubmitEnvironment方法:

//方法返回值是:(childArgs, childClasspath, sparkConf, childMainClass)

//找到对应的代码
val clusterManager: Int = args.master match {
   
    //这里就是判断集群环境的代码;
      case "yarn" => YARN 
      case m if m.startsWith("spark") => STANDALONE
      case m if m.startsWith("mesos") => MESOS
      case m if m.startsWith("k8s") => KUBERNETES
      case m if m.startsWith("local") => LOCAL 
      case _ =>
        error("Master must either be yarn or start with spark, mesos, k8s, or local")
        -1
    }

......
    
if (isYarnCluster) {
   
    //714行
      childMainClass = YARN_CLUSTER_SUBMIT_CLASS
          //这个值实际上就是:YARN_CLUSTER_SUBMIT_CLASS =
    "org.apache.spark.deploy.yarn.YarnClusterApplication"
      ......
          
    }
//所以之前我们要找的childMainClass就是org.apache.spark.deploy.yarn.YarnClusterApplication;这里可以通过引入Yarn的依赖spark-yarn_2.12后查找:
//以下是查找到的代码
private[spark] class YarnClusterApplication extends SparkApplication {
   
   

  override def start(args: Array[], :  Unit = {
   
   
    // SparkSubmit would use yarn cache to distribute files & jars in yarn mode,
    // so remove them from sparkConf here for yarn mode.
    conf.remove(JARS)
    conf.remove(FILES)

    new Client(new ClientArguments(args), conf, null).run() //这里会创建一个客户端对象;
  }

}
//所以根据之前的代码mainClass.getConstructor().newInstance().asInstanceOf[SparkApplication]会创建一个YarnClusterApplication对象;(即图中下部橙框部分内容)

在这里插入图片描述

1.3、Yarn集群分析:

//继续衔接刚才的代码:
//Client类中有一个非常重要的变量:
    private val yarnClient = YarnClient.createYarnClient
        //进入YarnClient.createYarnClient方法中:
    public static YarnClient createYarnClient() {
   
   
        YarnClient client = new YarnClientImpl();
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值