从源码的角度分析Spark的Yarn模式的任务提交部署流程

由于工作中生产环境大多使用的是Yarn-cluster模式,所以我将以Yarn-cluster模式作为主线对流程进行讲解。

目录

1.submit

2.client

3.ApplicationMaster(ExecutorLauncher)


现在我们提交一个spark任务

spark-submit \

  --master yarn-cluster  \

  --driver-cores 2   \

--driver-memory 1g \

  --executor-cores 4 \

  --num-executors 10 \

  --executor-memory 8g \

  --class PackageName.ClassName XXXX.jar \

  --name "Spark Job Name" \

  --queue spark \

  InputPath      \

  OutputPath

此时相当于执行了 java SparkSubmit -xxx  。。。。。。

相当于开启了一个JVM进程,执行了SparkSubmit类中的main方法。找到SparkSumit.scala类

1.submit

  def main(args: Array[String]): Unit = {
    val appArgs = new SparkSubmitArguments(args)
    if (appArgs.verbose) {
      // scalastyle:off println
      printStream.println(appArgs)
      // scalastyle:on println
    }
    appArgs.action match {
      case SparkSubmitAction.SUBMIT => submit(appArgs)
      case SparkSubmitAction.KILL => kill(appArgs)
      case SparkSubmitAction.REQUEST_STATUS => requestStatus(appArgs)
    }
  }

首先val appArgs = new SparkSubmitArguments(args) 封装参数,同时判断参数合法性等。

再进行模式匹配,提交时会走SparkSubmitAction.SUBMIT => submit(appArgs)

注意:在SparkSubmitArguments.scala类中有这样一段代码

    // Action should be SUBMIT unless otherwise specified
    action = Option(action).getOrElse(SUBMIT)
  }

因此此处的模式匹配默认会进行提交,所以不用添加case_  的条件。

现在继续:

进入 submit(appArgs)方法,首先开始准备提交环境

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

注意此处的类型推断,直接将返回值赋给了childArgs, childClasspath, sysProps, childMainClass这几个变量(这部分一会说,标记为,先往下走)

下面会执行doRunmain()方法,其中也包括runMain方法,主要区别在于是否使用代理用户,此处可以理解为方法统一:

 runMain(childArgs, childClasspath, sysProps, childMainClass, args.verbose)

进入runMain方法

此方法中:通过反射,加载了类(childMainClass类,是参数中的其中一个)

mainClass = Utils.classForName(childMainClass)

再找到这个类中的main方法

val mainMethod = mainClass.getMethod("main", new Array[String](0).getClass)

最后再通过invoke,调用该类的main方法

mainMethod.invoke(null, childArgs.toArray)

注意:由于此时是通过反射机制条用的main方法,所以不产生JVM进程,而是在SparkSubmit进程中创建了一个此方法的线程。

那现在执行的childMainClass这个类是谁呢?

现在回过头看标记

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

进入prepareSubmitEnvironment方法中

这个方法非常的长(通过返回值childArgs, childClasspath, sysProps, childMainClass可以找到这个方法的结尾),从判断条件中可以找到:

首先

    if (deployMode == CLIENT || isYarnCluster) {
      childMainClass = args.mainClass

然后:

    if (isYarnCluster) {
      childMainClass = "org.apache.spark.deploy.yarn.Client"

所以 在cluster的时候,childMainClass =》org.apache.spark.deploy.yarn.Client

在client的时候,childMainClass =》 参数中提交的类args.mainClass

到此,clister现在的部署的图形为:

2.client

进入org.apache.spark.deploy.yarn.Client类。

如果找不到需要在pom中引入依赖,再刷新

        <dependency>
            <groupId>org.apache.spark</groupId>
            <artifactId>spark-yarn_2.11</artifactId>
            <version>2.1.1</version>
        </dependency>

首先找到main方法

先封装参数:

val args = new ClientArguments(argStrings)

然后:

new Client(args, sparkConf).run()

此处先通过构造方法new了一个Client对象,对象属性中包括:

private val yarnClient = YarnClient.createYarnClient

从而得到了访问Yarn的客户端对象,通过这个对象向Yarn提交任务。这个对象再执行run()方法

进入run()方法以后

this.appId = submitApplication()

通过submitApplication方法,返回额一个id,用于所有后续应用调度,任务监控页面展示等等,非常重要。

现在进入submitApplication,顾名思义,这个是提交任务的方法。

首先进行后台连接,用于通信。

launcherBackend.connect()

结尾通过yarnClient的提交方法进行提交

yarnClient.submitApplication(appContext)

其中:参数appContenxt决定了提交内容,具体提交了什么呢?在最终提交的上面有这样一段代码,说明了提交的内容:

      val containerContext = createContainerLaunchContext(newAppResponse)
      val appContext = createApplicationSubmissionContext(newApp, containerContext)

进入createContainerLaunchContext方法

方法中定义了很多JVM相关参数,如堆大小,栈大小,CMS垃圾回收器参数等,其中:

    // Add Xmx for AM memory
    javaOpts += "-Xmx" + amMemory + "m"

此处说名,正在为ApplicationMaster设置进程参数。

    val amClass =
      if (isClusterMode) {
        Utils.classForName("org.apache.spark.deploy.yarn.ApplicationMaster").getName
      } else {
        Utils.classForName("org.apache.spark.deploy.yarn.ExecutorLauncher").getName
      }

显然:yarn-cluster模式是org.apache.spark.deploy.yarn.ApplicationMaster,yarn-client模式是org.apache.spark.deploy.yarn.ExecutorLauncher,执行类中主方法,开启了一个JVM进程

此处对不同提交模式设置不同的ApplicationMater执行类(此处也间接说明,不管yarn-cluster还是yarn-client模式,在创建ApplicationMaster时都会创建Client对象)

进入ApplicationMaster类,在最后

object ExecutorLauncher {

  def main(args: Array[String]): Unit = {
    ApplicationMaster.main(args)
  }

}

显然说明了,yarn-client的ExecutorLauncher最后调的也是ApplicationMater,可以认为是等价的了。

此时,调度流程图为:

3.ApplicationMaster(ExecutorLauncher)

进入ApplicationMaster中,找到main方法

第一步同样封装参数:

 val amArgs = new ApplicationMasterArguments(args)

创建应用管理器对象,并执行run方法

      master = new ApplicationMaster(amArgs, new YarnRMClient)
      System.exit(master.run())

进入run方法

      if (isClusterMode) {
        runDriver(securityMgr)
      } else {
        runExecutorLauncher(securityMgr)
      }

进入cluster模式的runDriver方法

首先启动用户应用

userClassThread = startUserApplication()

进入startUserApplication方法:获取用户应用类的main方法

    val mainMethod = userClassLoader.loadClass(args.userClass)
      .getMethod("main", classOf[Array[String]])

创建了一个用户线程,把这个线程起名为 Driver,并调用start方法启动

val userThread = new Thread {
 override def run() {
。。。
    }
    userThread.setContextClassLoader(userClassLoader)
    userThread.setName("Driver")
    userThread.start()
    userThread
  }

Driver线程进行执行,现在回到runDriver方法,继续往下走,开始向Yarn注册ApplicationMaster,建立两者的关系;两个进程之间使用rpc(参数中的rpcEnv)进行交互

 registerAM(sc.getConf, rpcEnv, driverRef, sc.ui.map(_.appUIAddress).getOrElse(""),securityMgr)

进入registerAM方法

代码最后部分:获取Yarn资源

    allocator = client.register(driverUrl,
      driverRef,
      yarnConf,
      _sparkConf,
      uiAddress,
      historyAddress,
      securityMgr,
      localResources)

追溯其中的client,是ApplicationMaster中的yarnRMClient,所以此处注册就是Driver向ResourceManager进行注册,目的是申请资源。

获取到之后,开始分配资源:

allocator.allocateResources()

进入allocateResources方法:拿到可分配的资源:

    val allocateResponse = amClient.allocate(progressIndicator)

    val allocatedContainers = allocateResponse.getAllocatedContainers()

拿到之后,处理资源:

 handleAllocatedContainers(allocatedContainers.asScala)

再进入handleAllocatedContainers方法,一系列本地化(涉及数据移动优化等,不详细说了)后,得到需要运行的container

然后开始运行可用的资源容器:

runAllocatedContainers(containersToUse)

进入runAllocatedContainers方法:

          launcherPool.execute(new Runnable {
            override def run(): Unit = {
              try {
                new ExecutorRunnable(
                  Some(container),
                  conf,
                  sparkConf,
                  driverUrl,
                  executorId,
                  executorHostname,
                  executorMemory,
                  executorCores,
                  appAttemptId.getApplicationId.toString,
                  securityMgr,
                  localResources
                ).run()

创建线程池,ExecutorRunnable(...).run()

点进new ExecutorRunnable中:

  var rpc: YarnRPC = YarnRPC.create(conf)
  var nmClient: NMClient = _

  def run(): Unit = {
    logDebug("Starting Executor Container")
    nmClient = NMClient.createNMClient()
    nmClient.init(conf)
    nmClient.start()
    startContainer()
  }

显然此处是在得到资源后,与其他NameNode进行交互,启动container

点击进入startContainer方法

val commands = prepareCommand()

进入prepareCommand方法

此方法中又出现了很多jvm参数,去执行一个进程

    val commands = prefixEnv ++ Seq(
      YarnSparkHadoopUtil.expandEnvironment(Environment.JAVA_HOME) + "/bin/java",
      "-server") ++
      javaOpts ++
      Seq("org.apache.spark.executor.CoarseGrainedExecutorBackend",
        "--driver-url", masterAddress,
        "--executor-id", executorId,
        "--hostname", hostname,
        "--cores", executorCores.toString,
        "--app-id", appId) ++
      userClassPath ++

此时运行的进程就是

org.apache.spark.executor.CoarseGrainedExecutorBackend

进入到CoarseGrainedExecutorBackend类中,可以找到main方法

main方法中:

      env.rpcEnv.setupEndpoint("Executor", new CoarseGrainedExecutorBackend(
        env.rpcEnv, driverUrl, executorId, hostname, cores, userClassPath, env))

定义了一个“Executor”用于进程间的交互,注意这个不是我们平时所说的Executor

进入new CoarseGrainedExecutorBackend,我们会发现:

var executor: Executor = null

这才是我们用来计算的Executor对象,是类中的一个属性。

其他节点后续交互部分就略过,会在下面的图中的流程中提示。到此yarn-cluster部署的流程到此结束,此时的流程图为:

 

最后附上比较权威的yarn-client和yarn-cluster的提交流程图

yarn-cluster模式

yarn-client模式:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值