Spark 之SparkContext 源码精读3

书接上文,SparkContext的CoarseGrainedSchedulerBackend已创建完毕,并且Driver也可以通过DriverEndpoint发消息了。

让咱们再回到CoarseGrainedSchedulerBackend的子类,SparkDeployScheduler.start方法中。至此super.start执行完毕。

继续往下看,获取driverUrl,也就是前述Rpc的地址;

第87行,创建了一个

1// SparkDeploySchedulerBackend.scala line 52 2override def start() { 3  super.start() 4  launcherBackend.connect() 5 6  // The endpoint for executors to talk to us 7  val driverUrl = rpcEnv.uriOf(SparkEnv.driverActorSystemName, 8    RpcAddress(sc.conf.get("spark.driver.host"), sc.conf.get("spark.driver.port").toInt), 9    CoarseGrainedSchedulerBackend.ENDPOINT_NAME) 10  val args = Seq( 11    "--driver-url", driverUrl, 12    "--executor-id", "{{EXECUTOR_ID}}", 13    "--hostname", "{{HOSTNAME}}", 14    "--cores", "{{CORES}}", 15    "--app-id", "{{APP_ID}}", 16    "--worker-url", "{{WORKER_URL}}") 17  val extraJavaOpts = sc.conf.getOption("spark.executor.extraJavaOptions") 18    .map(Utils.splitCommandString).getOrElse(Seq.empty) 19  val classPathEntries = sc.conf.getOption("spark.executor.extraClassPath") 20    .map(_.split(java.io.File.pathSeparator).toSeq).getOrElse(Nil) 21  val libraryPathEntries = sc.conf.getOption("spark.executor.extraLibraryPath") 22    .map(_.split(java.io.File.pathSeparator).toSeq).getOrElse(Nil) 23 24  // When testing, expose the parent class path to the child. This is processed by 25  // compute-classpath.{cmd,sh} and makes all needed jars available to child processes 26  // when the assembly is built with the "*-provided" profiles enabled. 27  val testingClassPath = 28    if (sys.props.contains("spark.testing")) { 29      sys.props("java.class.path").split(java.io.File.pathSeparator).toSeq 30    } else { 31      Nil 32    } 33 34  // Start executors with a few necessary configs for registering with the scheduler 35  val sparkJavaOpts = Utils.sparkJavaOpts(conf, SparkConf.isExecutorStartupConf) 36  val javaOpts = sparkJavaOpts ++ extraJavaOpts 37  // line 87 38  val command = Command("org.apache.spark.executor.CoarseGrainedExecutorBackend", 39    args, sc.executorEnvs, classPathEntries ++ testingClassPath, libraryPathEntries, javaOpts) 40  val appUIAddress = sc.ui.map(_.appUIAddress).getOrElse("") 41  val coresPerExecutor = conf.getOption("spark.executor.cores").map(_.toInt) 42  // line 91  43  val appDesc = new ApplicationDescription(sc.appName, maxCores, sc.executorMemory, 44    command, appUIAddress, sc.eventLogDir, sc.eventLogCodec, coresPerExecutor) 45  // line 93  46  client = new AppClient(sc.env.rpcEnv, masters, appDesc, this, conf) 47  client.start() 48  launcherBackend.setState(SparkAppHandle.State.SUBMITTED) 49  waitForRegistration() 50  launcherBackend.setState(SparkAppHandle.State.RUNNING) 51}

然后就是一些赋值操作

在第81行,特别关键,创建了Command对象,进入Command定义,一看吓一跳啊。第一个参数名是mainClass,顾名思义,就是入口类。

1// org.apache.spark.deploy.Command line 22 2private[spark] case class Command( 3    mainClass: String, 4    arguments: Seq[String], 5    environment: Map[String, String], 6    classPathEntries: Seq[String], 7    libraryPathEntries: Seq[String], 8    javaOpts: Seq[String]) { 9}

先按住兴奋,回到SparkDeploySchedulerBackend.start继续往下看。至91行,将上面创建的Command及maxCores、sc.executorMemory,coresPerExecutors等一些参数,一起创建了ApplicationDescription。感觉里真相越来越近了。继续下去。

第93行,用上面创建的ApplicationDescription及master【Array[String]】等其他参数,一起创建了AppClient。注意,这个构造中,倒数第二个参数是将this传递进去了。

1// AppClient.scala line 33 2/** 3 * Interface allowing applications to speak with a Spark deploy cluster. Takes a master URL, 4 * an app description, and a listener for cluster events, and calls back the listener when various 5 * events occur. 6 * 7 * @param masterUrls Each url should look like spark://host:port. 8 */ 9private[spark] class AppClient( 10    rpcEnv: RpcEnv, 11    masterUrls: Array[String], 12    appDescription: ApplicationDescription, 13    listener: AppClientListener, 14    conf: SparkConf)

注释很清晰的说明了,AppClient担任App和App所部署到的Spark集群沟通的角色。有master url ,app description,监听集群事件的监听器以及与各种监听到的集群的事件对应的回调。其中,倒数第二个参数是:AppClientListener,是一个trait。

1// AppClientListener.scala  2/** 3 * Callbacks invoked by deploy client when various events happen. There are currently four events: 4 * connecting to the cluster, disconnecting, being given an executor, and having an executor 5 * removed (either due to failure or due to revocation). 6 *  7 * Users of this API should *not* block inside the callback methods. 8 */ 9private[spark] trait AppClientListener { 10  def connected(appId: String): Unit 11 12  /** Disconnection may be a temporary state, as we fail over to a new Master. */ 13  def disconnected(): Unit 14 15  /** An application death is an unrecoverable failure condition. */ 16  def dead(reason: String): Unit 17 18  def executorAdded(fullId: String, workerId: String, hostPort: String, cores: Int, memory: Int) 19 20  def executorRemoved(fullId: String, message: String, exitStatus: Option[Int]): Unit 21}

注释很清晰的说明了,处理4件事,连接上集群、与集群断开连接、新增一个Executor,一个Executor被移除。

而实际在代码中,还有一个dead回调。是对断开连接的补充,断开连接是临时状态,若故障转移成功,则又回到连接状态,若失败,则dead。

看到这,再看之前传入的参数,是SparkDeploySchedulerBackend将自实例传入,一定是他或他的父类实现了AppClientListener。

1// SparkDeploySchedulerBackend.scala  2private[spark] class SparkDeploySchedulerBackend( 3    scheduler: TaskSchedulerImpl, 4    sc: SparkContext, 5    masters: Array[String]) 6  extends CoarseGrainedSchedulerBackend(scheduler, sc.env.rpcEnv) 7  with AppClientListener 8  with Logging

果然如此,实现了AppClientListener。

思路回到创建SparkDeploySchedulerBackend的 第94行。执行了AppClient.start

1// AppClient.scala line 281 2def start() { 3  // Just launch an rpcEndpoint; it will call back into the listener. 4  endpoint.set(rpcEnv.setupEndpoint("AppClient", new ClientEndpoint(rpcEnv))) 5}

又创建了ClientEndpoint。

1// AppClient.scala line 57 2private class ClientEndpoint(override val rpcEnv: RpcEnv) extends ThreadSafeRpcEndpoint 3  with Logging

请回忆前面的内容,是否有点眼熟?没错,Endpoint,实现了ThreadSafeRpcEndpoint。有印象吗?没错,是DriverEndpoint

同样的RpcEndpoint的生命周期是什么?复习下,没错,构造-> onStart ->receive* ->onStop。

了解下onStart,顾名思义,registerWithMaster,向Master注册。进入registerWithMaster瞅瞅。

1// AppClient.scala line 85 2override def onStart(): Unit = { 3  try { 4    registerWithMaster(1) 5  } catch { 6    case e: Exception => 7      logWarning("Failed to connect to master", e) 8      markDisconnected() 9      stop() 10  } 11}

进入registerWithMaster瞅瞅。

结合下面的代码和注释,很清晰的知道,Driver以多线程异步的方式向Master注册,一旦任意一个注册成功,则其他注册取消。若注册超时 ${REGISTRATION_TIMEOUT_SECONDS},会重试${REGISTRATION_RETRIES}次。

1// AppClient.scala line 125 2/** 3 * Register with all masters asynchronously. It will call `registerWithMaster` every 4 * REGISTRATION_TIMEOUT_SECONDS seconds until exceeding REGISTRATION_RETRIES times. 5 * Once we connect to a master successfully, all scheduling work and Futures will be cancelled. 6 * 7 *  8 */ 9private def registerWithMaster(nthRetry: Int) { 10  registerMasterFutures.set(tryRegisterAllMasters()) 11  registrationRetryTimer.set(registrationRetryThread.scheduleAtFixedRate(new Runnable { 12    override def run(): Unit = { 13      Utils.tryOrExit { 14        if (registered.get) { 15          registerMasterFutures.get.foreach(_.cancel(true)) 16          registerMasterThreadPool.shutdownNow() 17        } else if (nthRetry >= REGISTRATION_RETRIES) { 18          markDead("All masters are unresponsive! Giving up.") 19        } else { 20          registerMasterFutures.get.foreach(_.cancel(true)) 21          registerWithMaster(nthRetry + 1) 22        } 23      } 24    } 25  }, REGISTRATION_TIMEOUT_SECONDS, REGISTRATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)) 26} 27 28// AppClient.scala line 96 29/** 30 *  Register with all masters asynchronously and returns an array `Future`s for cancellation. 31 */ 32private def tryRegisterAllMasters(): Array[JFuture[_]] = { 33  for (masterAddress <- masterRpcAddresses) yield { 34    registerMasterThreadPool.submit(new Runnable { 35      override def run(): Unit = try { 36        if (registered.get) { 37          return 38        } 39        logInfo("Connecting to master " + masterAddress.toSparkURL + "...") 40        val masterRef = 41          rpcEnv.setupEndpointRef(Master.SYSTEM_NAME, masterAddress, Master.ENDPOINT_NAME) 42// line 109 43        masterRef.send(RegisterApplication(appDescription, self)) 44      } catch { 45        case ie: InterruptedException => // Cancelled 46        case NonFatal(e) => logWarning(s"Failed to connect to master $masterAddress", e) 47      } 48    }) 49  } 50}

具体看下消息的发收,AppClient的109 行,向master发送了一个RegisterApplication类型的消息。

1// DeployMessages.scala line 108 2case class RegisterApplication(appDescription: ApplicationDescription, driver: RpcEndpointRef) 3  extends DeployMessage

接收者是Master,回忆前面的RpcEndpoint,接收消息是receive*

1// Master.scala line 244 2case RegisterApplication(description, driver) => { 3  // TODO Prevent repeated registrations from some driver 4  if (state == RecoveryState.STANDBY) { 5    // ignore, don't send response 6  } else { 7    logInfo("Registering app " + description.name) 8    val app = createApplication(description, driver) 9// line 251 10    registerApplication(app) 11    logInfo("Registered app " + description.name + " with ID " + app.id) 12// line 253 13    persistenceEngine.addApplication(app) 14    driver.send(RegisteredApplication(app.id, self)) 15    schedule() 16  } 17}

将Application注册->持久化信息以备恢复->发一个RegisteredApplication消息给Driver->资源调度。

1// Master.scala line 795 2private def registerApplication(app: ApplicationInfo): Unit = { 3  val appAddress = app.driver.address 4  if (addressToApp.contains(appAddress)) { 5    logInfo("Attempted to re-register application at same address: " + appAddress) 6    return 7  } 8  applicationMetricsSystem.registerSource(app.appSource) 9  apps += app 10  idToApp(app.id) = app 11  endpointToApp(app.driver) = app 12  addressToApp(appAddress) = app 13  waitingApps += app 14}

简单的数据设置。master持久化driver后续会细细分析。

持久化成功后,Master会发送一条RegisteredApplication类型的消息给Driver,理解为你让我办的事,妥了。

1// DeployMessages.scala line 121 2case class RegisteredApplication(appId: String, master: RpcEndpointRef) extends DeployMessage

Driver接收到消息

1// AppClient.scala line 160 2case RegisteredApplication(appId_, masterRef) => 3  appId.set(appId_) 4  registered.set(true) 5  master = Some(masterRef) 6  listener.connected(appId.get)

Driver收到消息后,简单的做了些设置。理解为 "哦"。

至此,Driver向Master注册已经成功。

其实,本篇博客另一个关键的信息在于,展示了部署类消息都在 org.apache.spark.deploy.DeployMessages.scala。可以通过不同类型的消息,寻找对应的发送者和接收者,将流程贯穿起来。

如有理解错误,请大家回复我修改。谢谢。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )