Spark学习-2.4.0-源码分析-3-Spark 核心篇-Driver的启动注册流程分析

概述

在探究了SparkContext与SparkSubmit源码之后,我们大致了解了任务提交与SC的创建,但是有一个疑问就是:

  • 文章中所说的,在SparkContext初始化的过程中,会先注册Driver,再去注册Application。
  • 而SparkSubmit中也有提到Driver注册。
  • 那么,SaprkSubmit中的注册Driver与SC中的注册Driver,到底是怎么样的一个关系呢?
  • 感觉写的可能还是些问题的,可以看看Spark2.0.2源码分析——Driver的注册和启动

此外,问题在于有没有Driver注册这个阶段,这取决于集群部署模式:

  • 典型的集群模式,参考集群模式以及Client Mode和Cluster Mode的区别
  • 例如standalone模式,就是spark自己的一个独立集群,因此属于cluster模式,也就是说,在StandaloneAppClient在向Master注册Application时,不会像AppClient一样去先向Master注册Driver,因为它的Driver注册是交给了它自己指定的Worker。
  • 而像yarn-cluster这样的,Application不是交给Driver去管理,而是他们自身有例如ApplicationMaster这样的组件去进行管理。

大致流程

既然提到了这里,那么我们就要综合两个源码来进行分析了。
这里具体的执行流程大致如此,至于内部一些细节有待考察。

Master: Standalone模式中主控节点,负责接收Client提交的作业,管理Worker,并命令Worker启动Driver和Executor。
关于这句话,参考Spark架构与作业执行流程简介

值得注意的是,在Spark任务提交过程中,有两种Client:
一种是ClientApp,是用于启动、注册、终止Driver
一种是AppClient,是用于启动、注册、管理Application的。例如StandaloneAppClient。
之前一直以为StandaloneAppClient即会去注册Driver,又会去注册Application,这是错误的。

那么开始分析:

先看SparkSubmit.scala: :创建、注册ClientEndpoint,让ClientEndpoint获取用户程序并运行
 创建、注册ClientEndpoint :

  1. SparkSubmitMaster提交应用过程中,会创建loader用于加载Jar包,参见Spark-Submit
  2. 然后去获取Jar包中Client.mainClass-----即Client.scalaObject Client的的main方法
  3. 借助此mainClass.newInstance().asInstanceOf[SparkApplication]来创建一个SparkApplication( RestSubmissionClientApp或 ClientApp,根据网关提交方式确定)。
  4. ClientApp其实就是Driver,只是它现在还不叫Driver而已(在SparkContext.SparkEnv.RpcEnv中注册之后才叫Driver)。它内部会去创建ClientEndpoint(其实就是DriverEndpoint),负责与外界的通信。
  5. 创建ClientEndpoint时,在它的onStart()中,如果接收到的参数是launch,就会获取用户程序的main,以及一些依赖项、参数等等,封装为一个Command,然后创建一个DriverDescription(它包含了command)
  6. 接着,ClientEndpoint去向Master发送RequestSubmitDriver(driverDescription)(也就是它本身)。参见Spark Submit任务提交中的Driver注册
  7. Master接收到消息之后,就会注册Driver,并执行Driver的启动。也就是说, Master决定了Driver何时启动,而DriverDescription中包含了执行用户程序的Command。参见Spark之Driver启动(Driver在Worker上启动之后,就会,启动Executor,来执行用户的任务了)

 接着,Driver启动并运行用户程序:

  1. Driver的启动中,主要是,Master去让Worker启动Driver,而Worker中创建一个DriverRunner,来执行前面的DriverDescription中的Command—即用户程序main()参见Spark之Driver启动
  2. 在用户的main方法中,是会创建一个SparkContext,接下来就看SC的初始化中对Driver的操作。

SparkContext.scala::利用ClientEndpoint在RpcEnv中注册DriverEndpoint
在这里插入图片描述

  1. SparkContext源码分析一文中,在3.9 TaskScheduler启动中有提到,TaskScheduler在启动的时候实际是调用了StandaloneSchedulerBackendstart方法.
  2. 事实上,backend.start()方法又调用了父类的start()方法(StandaloneSchedulerBackend extends CoarseGrainedSchedulerBackend)
  3. super.start()中, 会在sc.env.rpcEnv创建一个DriverEndpoint()。这里,从它的定义可以看出来,这个创建用的参数都是前面SparkConf中的Properties,说明,这里的创建其实只是将SparkSubmit注册的ClientEndpoint到这里重新注册DriverEndpoint,参数、配置并未修改
    在这里插入图片描述

附录


我们就主要看看那Client的代码:

------------------------------class ClientEndpoint--------------------------
  override def onStart(): Unit = {
    driverArgs.cmd match {
      case "launch" =>
        // TODO: We could add an env variable here and intercept it in `sc.addJar` that would
        //       truncate filesystem paths similar to what YARN does. For now, we just require
        //       people call `addJar` assuming the jar is in the same directory.
        // TODO:我们可以在这里添加一个env变量,并在"sc.addJar"中拦截它,这将截文件系统路径,类似于YARN所做的。
        //       现在,我们只需要人们调用"addJar"假设这个jar在同一个目录中。
        val mainClass = "org.apache.spark.deploy.worker.DriverWrapper"

        val classPathConf = "spark.driver.extraClassPath"
        val classPathEntries = getProperty(classPathConf, conf).toSeq.flatMap { cp =>
          cp.split(java.io.File.pathSeparator)
        }

        val libraryPathConf = "spark.driver.extraLibraryPath"
        val libraryPathEntries = getProperty(libraryPathConf, conf).toSeq.flatMap { cp =>
          cp.split(java.io.File.pathSeparator)
        }

        val extraJavaOptsConf = "spark.driver.extraJavaOptions"
        val extraJavaOpts = getProperty(extraJavaOptsConf, conf)
          .map(Utils.splitCommandString).getOrElse(Seq.empty)

        val sparkJavaOpts = Utils.sparkJavaOpts(conf)
        val javaOpts = sparkJavaOpts ++ extraJavaOpts
        val command = new Command(mainClass,
          Seq("{{WORKER_URL}}", "{{USER_JAR}}", driverArgs.mainClass) ++ driverArgs.driverOptions,
          sys.env, classPathEntries, libraryPathEntries, javaOpts)

        val driverDescription = new DriverDescription(
          driverArgs.jarUrl,
          driverArgs.memory,
          driverArgs.cores,
          driverArgs.supervise,
          command)
        asyncSendToMasterAndForwardReply[SubmitDriverResponse](
          RequestSubmitDriver(driverDescription))

      case "kill" =>
        val driverId = driverArgs.driverId
        asyncSendToMasterAndForwardReply[KillDriverResponse](RequestKillDriver(driverId))
    }
  }
------------------------------object Client--------------------------
/**
 * Executable utility for starting and terminating drivers inside of a standalone cluster.
 * 用于在standaloneCluster集群内启动和终止Driver的公用可执行程序
 */
object Client {
  def main(args: Array[String]) {
    // scalastyle:off println
    if (!sys.props.contains("SPARK_SUBMIT")) {
      println("WARNING: This client is deprecated and will be removed in a future version of Spark")
      println("Use ./bin/spark-submit with \"--master spark://host:port\"")
    }
    // scalastyle:on println
    new ClientApp().start(args, new SparkConf())
  }
}
------------------------------class ClientApp--------------------------
private[spark] class ClientApp extends SparkApplication {

  override def start(args: Array[String], conf: SparkConf): Unit = {
    val driverArgs = new ClientArguments(args)

    if (!conf.contains("spark.rpc.askTimeout")) {
      conf.set("spark.rpc.askTimeout", "10s")
    }
    Logger.getRootLogger.setLevel(driverArgs.logLevel)

    // 先通过create()获取一个nettyEnv
    val rpcEnv =
      RpcEnv.create("driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))

    //通过master的地址和ENDPOINT_NAME(即"Master")来获取一个setupEndpointRef代理对象,即只需要去拿到这个Ref就可以
     // 因为在Master的main函数中,已经在startRpcEnvAndEndpoint函数中,
      // 通过调用rpcEnv.setupEndpoint()在rpcEnv中注册了masterEndpoint,这里不需要重复去注册。
    val masterEndpoints = driverArgs.masters.map(RpcAddress.fromSparkURL).
      map(rpcEnv.setupEndpointRef(_, Master.ENDPOINT_NAME))// val ENDPOINT_NAME = "Master"
      
    // 这里就注册一个clientEndpoint.
    rpcEnv.setupEndpoint("client", 
        // 在创建过程中,onStart()会去做初始化,并且创建一个DriverDescription
        // 然后通过这个masterEndpoints来发送消息,注册Driver
        new ClientEndpoint(rpcEnv, driverArgs, masterEndpoints, conf))

    rpcEnv.awaitTermination()
  }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值