Spark 任务调度之启动CoarseGrainedExecutorBackend

概要

本文基于Spark之Launch Executor一文中的启动流程之Worker端receive消息延伸讲解了Worker执行的Command中调用的CoarseGrainedExecutorBackend类

1. CoarseGrainedExecutorBackend简介

在介绍CoarseGrainedExecutorBackend的启动流程前,先了解下CoarseGrainedExecutorBackend。
我们知道Executor负责计算任务,即执行task,而Executor对象的创建及维护是由CoarseGrainedExecutorBackend负责的CoarseGrainedExecutorBackend在spark运行期是一个单独的进程,在Worker节点可以通过Java的jps命令查看,如下
在这里插入图片描述
如何启动上图中的CoarseGrainedExecutorBackend进程是我们后面介绍的重点,先看下其定义及UML
package org.apache.spark.scheduler.cluster.CoarseGrainedExecutorBackend.scala
在这里插入图片描述
在这里插入图片描述
从上图可以得到如下信息

  1. CoarseGrainedExecutorBackendRpcEndpoint的子类,能够和Driver进行RPC通信,其生命周期方法onStart一定要关注,看执行了哪些动作。
  2. CoarseGrainedExecutorBackend维护了两个属性executordriver : executor负责运行task,driver是自己所属的Driver的Ref,用于向Driver发消息。
  3. ExecutorBackend有抽象方法statusUpdate,负责将Executor的计算结果返回给Driver。
    最后,CoarseGrainedExecutorBackend是spark运行期的一个进程,Executor运行在该进程内。

除此之外,CoarseGrainedExecutorBackend有自己的NettyRpcEndpoint,基于此与Driver进行通信。

2. 启动CoarseGrainedExecutorBackend

接着Spark 任务调度之Launch Executor介绍,最后讲到

  • Worker进程收到LaunchExecutor消息,然后,Worker将收到的消息封装为ExecutorRunner对象,调用其start方法。
  • start方法启动线程,调用ExecutorRunnerfetchAndRunExecutor方法,fetchAndRunExecutor方法中将收到的信息拼接为Linux命令,然后使用ProcessBuilder执行Linux命令启动CoarseGrainedExecutorBackend(和启动Driver的方式如出一辙),执行的Linux命令大致如下 :
    在这里插入图片描述
    上面的java命令会调用CoarseGrainedExecutorBackend的main方法,main方法中处理命令行传入的参数,然后创建RpcEnv,并注册RpcEndpoint等。
    接下来看看main()方法的具体

2.1 main()

从上图可以看出来,命令行会给main方法传很多参数。因此,这些参数需要解析并保存起来:

2.1.1 定义变量用以保存命令行参数

在这里插入图片描述

2.1.2 解析命令行参数

在这里插入图片描述

2.1.3 调用run()来执行主要逻辑

  见下一节的----2.2 run()

2.2 run()

2.2.1 run()获取到的参数

查看run()函数的定义:
在这里插入图片描述
可以看到,run()函数得到的信息中,包含了DriverUrlExecutorId(由Master指定的)WorkerUrl等等。

2.2.2 从Driver获取properties

既然知道了Driver的地址,那么就作为SparkUser去从Driver拿到Spark的properties
在这里插入图片描述

2.2.3 创建ExecutorEnv

通过从Driver拿到的properties来为Executor创建ExecutorEnv
在内部,会去创建属于此Executor的NettyRpcEnv
,也就意味着它有自己的TransportContext、TransportServer等等组件了,可以进行通信。
在这里插入图片描述

2.2.3 创建ExecutorRpcEndpoint

通过ExecutorNettyRpcEnv来创建ExecutorRpcEndpoint,
- 通过传入一个CoarseGrainedExecutorBackend来创建的。
- 同时,会创建一个WorkerWatcher–RpcEndpoint,用于连接到Worker进程,并在连接断开是终止JVM

在这里插入图片描述
到这里,我们知道了,run()方法会去创建CoarseGrainedExecutorBackend。
也就是说,

  • 在Object的main()中调用run()
  • run()中创建了一个Class CoarseGrainedExecutorBackend

下面我们来看看,在new CoarseGrainedExecutorBackend时候会执行的onStart()方法

2.3 new CoarseGrainedExecutorBackend之onStart()

  onStart()方法中主要是获取到DriverRpcEndpointRef,从而向Driver发送ask(RegisterExecutor)。到Driver注册此Executor,Driver会去做执行注册等操作,参见Launch Executor之Driver端这里虽然向Driver注册了Executor,但是实际上只是将地址信息先告诉了Driver,然后等待Driver返回RegisteredExecutor,Executor才被真正的创建出来。
  值得注意的是由于Master只是起到一个资源调度和管理的作用,因此,这里Executor并不会去向Master注册,并且Master也不会像维护Driver和Worker那样去维护Executor的信息。

在这里插入图片描述
既然是ask,那么就会有异步的返回消息。他的返回消息就是RegisteredExecutor

2.4 处理返回消息RegisteredExecutor

在这里插入图片描述
既然Driver返回了注册成功的消息,那么就要去真正的创建出来这个Executor了。

  • 83行中,创建了一个Executor,executorId是之前由Master指定的。

至此,CoarseGrainedExecutorBackend就已经成功启动,并且创建了Executor,Executor创建时,会创建ThreadPool,并且去获取数据,从而执行Tasks。这部分我们放在Executor执行Task部分来进行剖析。

总结

本文基于Spark之Launch Executor一文中的启动流程之Worker端receive消息,延伸讲解了Worker执行的Command中调用的CoarseGrainedExecutorBackend类。

  • CoarseGrainedExecutorBackend是Executor的直接创建者,也就是说,它拥有一个Executor。CoarseGrainedExecutorBackend有自己的executorRpcEndpoint,Executor基于此进行通信
  • CoarseGrainedExecutorBackend在创建Executor之前,才会去向Driver注册Executor,直到Driver返回注册成功,才会去真正创建Executor,之前只是准备好了注册信息等。

在这里插入图片描述

致谢

附录

package org.apache.spark.executor

--------------------------CoarseGrainedExecutorBackend.scala:----------------------------
private[spark] object CoarseGrainedExecutorBackend extends Logging {

  private def run(
      driverUrl: String,
      executorId: String,
      hostname: String,
      cores: Int,
      appId: String,
      workerUrl: Option[String],
      userClassPath: Seq[URL]) {

    Utils.initDaemon(log)

    SparkHadoopUtil.get.runAsSparkUser { () =>
      // Debug code
      Utils.checkHost(hostname)
      
      // 1. 作为SparkUser去拿到Spark的properties。
      val executorConf = new SparkConf
      
          // 1.1 fetcher是由RpcEnv.create()创建的一个临时的NettyRpcEnv
         // 注意这个NettyRpcEnv只是用来获取Executor和App相关的配置信息等,获取完就会shutdown.
      val fetcher = RpcEnv.create(
        "driverPropsFetcher",
        hostname,
        -1,
        executorConf,
        new SecurityManager(executorConf),
        clientMode = true)
      val driver = fetcher.setupEndpointRefByURI(driverUrl)
      val cfg = driver.askSync[SparkAppConfig](RetrieveSparkAppConfig)
      val props = cfg.sparkProperties ++ Seq[(String, String)](("spark.app.id", appId))
      fetcher.shutdown()

      // 2. 通过从Driver拿到的properties来创建ExecutorEnv,
      //    在内部,会去创建属于此Executor的NettyRpcEnv,也就意味着它有自己的TransportContext、TransportServer等等组件了,可以进行通信。
      val driverConf = new SparkConf()
      for ((key, value) <- props) {
        // this is required for SSL in standalone mode
        if (SparkConf.isExecutorStartupConf(key)) {
          driverConf.setIfMissing(key, value)
        } else {
          driverConf.set(key, value)
        }
      }

      cfg.hadoopDelegationCreds.foreach { tokens =>
        SparkHadoopUtil.get.addDelegationTokens(tokens, driverConf)
      }

      val env = SparkEnv.createExecutorEnv(
        driverConf, executorId, hostname, cores, cfg.ioEncryptionKey, isLocal = false)
        
      // 3. 通过ExecutorNettyRpcEnv来创建ExecutorRpcEndpoint,
            // 通过传入一个CoarseGrainedExecutorBackend来创建的。
      env.rpcEnv.setupEndpoint("Executor", new CoarseGrainedExecutorBackend(
        env.rpcEnv, driverUrl, executorId, hostname, cores, userClassPath, env))
      workerUrl.foreach { url =>
        // 3.* 创建一个WorkerWatcher--RpcEndpoint,用于连接到Worker进程并在连接断开时终止jvm
        env.rpcEnv.setupEndpoint("WorkerWatcher", new WorkerWatcher(env.rpcEnv, url))
      }
      env.rpcEnv.awaitTermination()
    }
  }

  def main(args: Array[String]) {
    var driverUrl: String = null
    var executorId: String = null
    var hostname: String = null
    var cores: Int = 0
    var appId: String = null
    var workerUrl: Option[String] = None
    val userClassPath = new mutable.ListBuffer[URL]()

    var argv = args.toList
    while (!argv.isEmpty) {
      argv match {
        case ("--driver-url") :: value :: tail =>
          driverUrl = value
          argv = tail
        case ("--executor-id") :: value :: tail =>
          executorId = value
          argv = tail
        case ("--hostname") :: value :: tail =>
          hostname = value
          argv = tail
        case ("--cores") :: value :: tail =>
          cores = value.toInt
          argv = tail
        case ("--app-id") :: value :: tail =>
          appId = value
          argv = tail
        case ("--worker-url") :: value :: tail =>
          // Worker url is used in spark standalone mode to enforce fate-sharing with worker
          workerUrl = Some(value)
          argv = tail
        case ("--user-class-path") :: value :: tail =>
          userClassPath += new URL(value)
          argv = tail
        case Nil =>
        case tail =>
          // scalastyle:off println
          System.err.println(s"Unrecognized options: ${tail.mkString(" ")}")
          // scalastyle:on println
          printUsageAndExit()
      }
    }

    if (driverUrl == null || executorId == null || hostname == null || cores <= 0 ||
      appId == null) {
      printUsageAndExit()
    }

    run(driverUrl, executorId, hostname, cores, appId, workerUrl, userClassPath)
    System.exit(0)
  }
-------------------------------------onStart()------------
 override def onStart() {
    logInfo("Connecting to driver: " + driverUrl)
    rpcEnv.asyncSetupEndpointRefByURI(driverUrl).flatMap { ref =>
      // This is a very fast action so we can use "ThreadUtils.sameThread"
      driver = Some(ref)
      ref.ask[Boolean](RegisterExecutor(executorId, self, hostname, cores, extractLogUrls))
    }(ThreadUtils.sameThread).onComplete {
      // This is a very fast action so we can use "ThreadUtils.sameThread"
      case Success(msg) =>
        // Always receive `true`. Just ignore it
      case Failure(e) =>
        exitExecutor(1, s"Cannot register with driver: $driverUrl", e, notifyDriver = false)
    }(ThreadUtils.sameThread)
  }


------------------------------receive()-------------------
    case RegisteredExecutor =>
      logInfo("Successfully registered with driver")
      try {
        executor = new Executor(executorId, hostname, env, userClassPath, isLocal = false)
      } catch {
        case NonFatal(e) =>
          exitExecutor(1, "Unable to create executor due to " + e.getMessage, e)
      }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值