spark2.4.3(一)提交作业的整体流程

本文主要内容:

  • spark standalone模式的 job执行流程;
  • sparkContext主要作用;
  • Master选主原理;
  • Master主备切换原理等

作业提交和执行流程

以standalone模式为例的基本流程:

  1. spark-submit 提交代码,执行 new SparkContext(),在 SparkContext 里构造 DAGScheduler 和 TaskScheduler。
  2. TaskScheduler 会通过后台的一个进程,连接 Master,向 Master 注册 Application。
  3. Master 接收到 Application 请求后,会使用相应的资源调度算法,在 Worker 上为这个 Application 启动多个 Executer。
  4. Executor 启动后,会自己反向注册到 TaskScheduler 中。 所有 Executor 都注册到 Driver 上之后,SparkContext 结束初始化,接下来往下执行我们自己的代码。
  5. 每执行到一个 Action,就会创建一个 Job。Job 会提交给 DAGScheduler。
  6. DAGScheduler 会将 Job划分为多个 stage,然后每个 stage 创建一个 TaskSet。
  7. TaskScheduler 会把每一个 TaskSet 里的 Task,提交到 Executor 上执行。
  8. Executor 上有线程池,每接收到一个 Task,就用 TaskRunner 封装,然后从线程池里取出一个线程执行这个 task。(TaskRunner 将我们编写的代码,拷贝,反序列化,执行 Task,每个 Task 执行 RDD 里的一个 partition)

下面再赘述一遍哈哈:

spark-submit shell提交Application,提交到Driver进程。Spark使用DriverWrapper启动用户APP的main函数,如图。

Master Worker SparkSubmit Driver StandaloneAppClien 集群启动,Master/Worker 启动 发送registerWorker消息 HeartBeat loop [ HB timeout / 4 ms ] handleSubmit() RequestSubmitDriver(driverDescription)消息 【1】创建出Driver, 开启调度 创建出Driver对象 RegisterApplication消息,请求提交App RegisteredApplication 记录app信息,执行 调度,挨个启动wai tingDrivers 【2】 LaunchDriver消息 在线程中启动Driver进程 prepareAndRunDriver() Driver执 行用户自定义程序 startExecutorsOnWorkers() 【3】LaunchExecutor消息 启动ExecutorRunner 回复ExecutorStateChanged消息 ExecutorAdded消息 Master Worker SparkSubmit Driver StandaloneAppClien

Driver进程执行Application应用代码,我们的代码里先getOrCreate() 构建sparkContext。

sparkContext初始化时,主要是构建出TaskScheduler和DAGScheduler。

TaskScheduler通过后台进程连接Master,向Master注册Application.
Master收到注册请求后,通知集群的Worker(资源调度算法),为这个Application启动多个Executor.
Executor启动后,各自反向注册到Driver上的TaskScheduler,
之后Driver继续执行Application自己的代码。
每执行到一个Action,就会创建一个Job,提交给DAGScheduler。

DAGScheduler会将job划分为多个stage(划分算法),然后每个stage创建一个TaskSet。
TaskScheduler把TaskSet里每一个task提交到Executor上去执行(task分配算法)。

Executor接收到task会用TaskRunner来封装task,然后从线程池中取出一个线程,准备执行这个task.
TaskRunner将算子和函数拷贝,反序列化,然后执行task.

Task有两种,ShuffleMapTask和ResultTask,最后一个stage是ResultTask.

spark程序的执行,就是stage分批次作为taskset提交到executor执行,
每个task针对RDD的一个partition,执行我们定义的算子和函数。


源码spark2.4.3

1. SparkContext.scala

位于core - org.apache.spark

    // 创建并启动 TaskScheduler
    val (sched, ts) = SparkContext.createTaskScheduler(this, master, deployMode)
    _schedulerBackend = sched
    _taskScheduler = ts
    // 创建 DAGScheduler
    _dagScheduler = new DAGScheduler(this)
    _heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)

    // start TaskScheduler after taskScheduler sets DAGScheduler reference in DAGScheduler's constructor
    _taskScheduler.start()
    
    ......
    
    //注册spark指定的监听器,然后启动监听器总线 listenerBus。
    setupAndStartListenerBus()
    // TaskScheduler启动后,用listenerBus Post环境更新事件
    postEnvironmentUpdate()
    // Post 应用启动事件 (by listenerBus)
    postApplicationStart()
    _taskScheduler.postStartHook()

SparkContext主要工作:

  1. 创建TaskScheduler
  2. 调用TaskSchedulerImpl的start()
  3. 创建了DAGScheduler

下面分开看。

1.1 创建TaskScheduler

createTaskScheduler()方法

根据不同的Master启动模式,分别处理。分为Local模式,Standalone模式,Cluster模式三类。


        //只看Standalone模式

        //sparkUrl: 集群的 master URL
        case SPARK_REGEX(sparkUrl) =>
          val scheduler = new TaskSchedulerImpl(sc)
          val masterUrls = sparkUrl.split(",").map("spark://" + _)
          val backend = new StandaloneSchedulerBackend(scheduler, sc, masterUrls)
          scheduler.initialize(backend)
          (backend, scheduler) //返回值

先new一个 TaskSchedulerImpl,然后用某种SchedulerBackend来初始化。

  • TaskScheduler通过一个SchedulerBackend, 针对不同的cluster模式来调度task。如果使用LocalSchedulerBackend,并将isLocal设为true,则可以工作在本地模式下。
  • TaskScheduler负责处理通用逻辑,比如决定多个job的调度顺序,启动推测任务执行
  • 使用时,应先调用initialize()和start()方法,然后再通过submitTasks()方法提交task Set.

initialize()方法根据调度模式是FIFO还是FAIR,创建不同的SchedulableBuilder,调用其buildPools()方法,创建一个调度池。

1.2 调用TaskSchedulerImpl的start()

TaskSchedulerImpl的start(),调用的是SchedulerBackend的start()。

StandaloneSchedulerBackend的start()方法主要逻辑:


    // ApplicationDescription是一个Wrapper
    // 包含了submit脚本的参数,例如num-executors,executor-memory,executor-core 等
    val appDesc = ApplicationDescription(sc.appName, maxCores, sc.executorMemory, command,
      webUrl, sc.eventLogDir, sc.eventLogCodec, coresPerExecutor, initialExecutorLimit)

    // AppClient,负责app与spark集群通信,它会接收一个spark master url,ApplicationDescription
    // 和一个集群事件的监听器,把各种事件回调给监听器。
    // AppClient有一个内部类ClientActor,去连接某个master的url,发送RegisterApplication消息。
    client = new StandaloneAppClient(sc.env.rpcEnv, masters, appDesc, this, conf)
    client.start()

    // 保存变更状态
    launcherBackend.setState(SparkAppHandle.State.SUBMITTED)
    waitForRegistration()
    launcherBackend.setState(SparkAppHandle.State.RUNNING)
1.3 创建DAGScheduler

package org.apache.spark.scheduler下的DAGScheduler.scala

  • DAGScheduler作为high-level层的调度,实现了面向stage的调度。它计算DAG每个作业的stages,跟踪哪些RDDs和stage输出被实体化,并找到最小调度计划来运行job。然后它将stages作为TaskSets提交给具体的TaskScheduler实现,并在集群上运行。

   TaskSet包含了完全独立的任务,可以基于集群中已有的数据(比如,前一stage中map()的输出文件)立即运行。如果该数据变得不可用,它可能会失败。

  • DAGScheduler还决定了要在其上运行的每个task的最佳位置,并将这些位置传递给low-level的TaskScheduler。
    此外,它还处理由于shuffle输出文件丢失而导致的故障。这种情况下,旧的stages可能需要重新提交。
    一个stage内部的失败,如果不是由于shuffle文件丢失所导致的,会被TaskScheduler重试几次,直到最后仍然失败,才会撤销整个stage.

2. Master.scala

位于core下的package org.apache.spark.deploy.master

Master主要负责:

  1. Leader选举
    选举成为Leader后,如果状态是RECOVERING就恢复storedApps, storedDrivers, storedWorkers重新调度;
  2. 管理Application
    createApplication/registerApplication/UnregisterApplication/removeApplication
  3. 管理Driver
    createDriver/launchDriver/removeDriver/KillDriver/relaunchDriver
  4. 管理Workers以及worker上的Executors
    RegisterWorker/removeWorker
    scheduleExecutorsOnWorkers/startExecutorsOnWorkers/allocateWorkerResourceToExecutors/launchExecutor/handleRequestExecutors/handleKillExecutors
  5. 处理状态改变ExecutorStateChanged
  6. 主备切换completeRecovery
2.1 leader选举详解

Master内部我们称"leader选举",Master外部我们称为“Master选举”。

Spark支持以下几种选主(Master选举)策略,这种策略可以通过配置文件spark-env.sh配置spark.deploy.recoveryMode。

  • ZOOKEEPER: 集群元数据持久化到zookeeper,当master出现异常的时候,zookeeper会通过选举机制选举出新的Master,新的Master接管集群时需要从zookeeper获取持久化信息,并根据这些信息恢复集群状态。
  • FILESYSTEM: 集群的元数据持久化到文件系统,当Master出现异常的时候,只要在该机器上重启Master,启动后的Master获取持久化信息并根据持久化信息恢复集群状态。
  • CUSTOM: 自定义恢复模式,实现StandaloneRecoveryModeFactory抽象类进行实现,并把该类配置到配置文件,当Master出现异常,会根据用户自定义的方式进行恢复集群状况。
  • NONE: 不持久化集群元数据,当Master出现异常时,新启动的Master不进行恢复集群状态。

Apache Curator是一个Zookeeper的开源客户端,它提供了Zookeeper各种应用场景(Recipe,如共享锁服务、master选举、分布式计数器等)的抽象封装。
Curator提供了两种选举方案:Leader Latch 和 Leader Election。
Spark的Zookeeper模式使用的是Leader Latch选举方式。

master onStart()时候,根据当前配置的spark.deploy.recoveryMode策略,比如Zookeeper,初始化这两个引擎:
org.apache.spark.deploy.master.ZooKeeperLeaderElectionAgent.scala
org.apache.spark.deploy.master.ZooKeeperPersistenceEngine

ZooKeeperLeaderElectionAgent会在构造时,执行start()方法。

在ZK的选举目录发生变化时,会自动触发Watch,执行回调方法。选举成功时回调isLeader(),落选时回调notLeader()。
回调中执行updateLeadershipStatus()更新状态。


    // WORKING_DIR就是要监听的ZK目录
    var WORKING_DIR = conf.get("spark.deploy.zookeeper.dir", "/spark") + "/leader_election"

    private def start() {
    logInfo("Starting ZooKeeper LeaderElection agent")
    zk = SparkCuratorUtil.newClient(conf)
    // 用WORKING_DIR构造LeaderLatch
    leaderLatch = new LeaderLatch(zk, WORKING_DIR)
    // 添加监听器,ZooKeeperLeaderElectionAgent继承了LeaderLatchListener,
    // 并且实现了isLeader/notLeader等方法。
    leaderLatch.addListener(this)
    // 启动LeaderLatch
    leaderLatch.start()
  }

  override def stop() {
    leaderLatch.close()
    zk.close()
  }

  override def isLeader() {
    synchronized {
      // could have lost leadership by now.
      if (!leaderLatch.hasLeadership) {
        return
      }

      logInfo("We have gained leadership")
      updateLeadershipStatus(true)
    }
  }

  override def notLeader() {
    synchronized {
      // could have gained leadership by now.
      if (leaderLatch.hasLeadership) {
        return
      }

      logInfo("We have lost leadership")
      updateLeadershipStatus(false)
    }
  }

  private def updateLeadershipStatus(isLeader: Boolean) {
    if (isLeader && status == LeadershipStatus.NOT_LEADER) {
      status = LeadershipStatus.LEADER
      // 执行Master的electedLeader()方法。
      masterInstance.electedLeader()
    } else if (!isLeader && status == LeadershipStatus.LEADER) {
      status = LeadershipStatus.NOT_LEADER
      masterInstance.revokedLeadership()
    }
  }

Master继承了LeaderElectable,并实现了electedLeader方法。因此在updateLeadershipStatus时,
Master执行:

  override def electedLeader() {
    self.send(ElectedLeader)
  }

给发个消息通知自己,读取持久化引擎的数据用来恢复:

    case ElectedLeader =>
      val (storedApps, storedDrivers, storedWorkers) = persistenceEngine.readPersistedData(rpcEnv)
      state = if (storedApps.isEmpty && storedDrivers.isEmpty && storedWorkers.isEmpty) {
        RecoveryState.ALIVE
      } else {
        RecoveryState.RECOVERING
      }
      logInfo("I have been elected leader! New state: " + state)
2.2 处理Application消息

创建Application,registerApplication(),添加到持久化引擎,发送注册完成消息,最后调用schedule()。

createApplication()返回一个ApplicationInfo对象

registerApplication()传入的app的driver地址已存在,就不再注册。否则把app加入到apps/idToApp/actorToApp(内存缓存),以及waitingApps这个等待调度的队列(FIFO ArrayBuffer)。
发送注册完成消息:向SparkDeploySchedulerBackend的AppClient的ClientActor发送消息(Registered)。

2.3 管理Driver
2.4 管理Workers以及worker上的Executors
2.5 处理状态改变

case DriverStateChanged

如果driver的状态是错误、完成、被杀掉、失败,就移除之:
removeDriver()用scala的find()找到id对应的driver,
从内存缓冲中清除,加入到completeedDrivers,使用持久化引擎去除driver的信息,设置driver的状态,将driver所在的worker移除,最后调用schedule()。

case ExecutorStateChanged

从idToApp内存缓存中查找Executor,如果找到就更新状态,向driver同步发送ExecutorUpdated消息,
判断如果Executor完成了,就从appInfo中移除,从运行executor的worker缓存中移除。
判断如果Executor的退出状态是非正常的,如果application重试次数小于最大值,就重新schedule(),超过了最大次数,认为Application失败了,就removeApplication()。

2.6 主备切换(Master恢复)

Master 异常恢复流程:

  1. 首先完成选举,成为leader。见上述2.1。
  2. 执行beginRecovery()开始恢复
  3. worker接收到MasterChange消息,向master发送消息:WorkerSchedulerStateResponse
  4. Application接收到MasterChange消息,向Master发送消息:MasterChangeAcknowledged
  5. Master接收到WorkerSchedulerStateResponse和MasterChangeAcknowledged消息后,调用completeRecovery操作
  6. 执行completeRecovery()完成恢复

beginRecovery()
选主成功,从持久化引擎读取storedApps, storedDrivers, storedWorkers,

  • 对每个app/worker,重新注册给本Master的内存记录,修改状态为UNKNOWN,通知app的driver发送MasterChanged消息。
  • 对每个driver,just存入driver列表。

worker.scala收到MasterChanged
org.apache.spark.deploy.worker

case MasterChanged(masterRef, masterWebUiUrl) =>
      logInfo("Master has changed, new master is at " + masterRef.address.toSparkURL)
      //获取新的master的url和master地址,连接状态置为true, 取消之前的重新注册的attempts
      changeMaster(masterRef, masterWebUiUrl, masterRef.address)

      val execs = executors.values.
        map(e => new ExecutorDescription(e.appId, e.execId, e.cores, e.state))
      // 向新的master发送WorkerSchedulerStateResponse消息
      masterRef.send(WorkerSchedulerStateResponse(workerId, execs.toList, drivers.keys.toSeq))

Application收到MasterChanged
org.apache.spark.deploy.client.StandaloneAppClient.scala

case MasterChanged(masterRef, masterWebUiUrl) =>
        logInfo("Master has changed, new master is at " + masterRef.address.toSparkURL)
        master = Some(masterRef)
        alreadyDisconnected = false
        masterRef.send(MasterChangeAcknowledged(appId.get))

completeRecovery()
收到MasterChangeAcknowledged/WorkerSchedulerStateResponse消息,或者Master onDisconnected的时候,如果canCompleteRecovery为true,可以执行completeRecovery()来完成Master恢复。

将Application和Worker过滤出目前状态还是Unknown的,分别调用removeWorker和finishApplication方法,对可能已经出故障,甚至已经死掉的Application和Worker,进行清理。
清理包括:1、从内存缓存结构中移除;2、从相关的组件的内存缓存中移除; 3、从持久化存储中移除。最后调用schedule().

def canCompleteRecovery =
    workers.count(_.state == WorkerState.UNKNOWN) == 0 &&
      apps.count(_.state == ApplicationState.UNKNOWN) == 0

private def completeRecovery() {
    // 使用一个较小的同步周期,来确保"Only-Once" 的恢复语义。
    if (state != RecoveryState.RECOVERING) { return }
    state = RecoveryState.COMPLETING_RECOVERY

    // 消灭所有不再回应的workers和apps
    workers.filter(_.state == WorkerState.UNKNOWN).foreach(
      removeWorker(_, "Not responding for recovery"))
    // 执行finishApplication方法
    apps.filter(_.state == ApplicationState.UNKNOWN).foreach(finishApplication)

    // 把恢复的apps状态改为RUNNING
    apps.filter(_.state == ApplicationState.WAITING).foreach(_.state = ApplicationState.RUNNING)

    // 重新调度workers为空的的Drivers
    drivers.filter(_.worker.isEmpty).foreach { d =>
      logWarning(s"Driver ${d.id} was not found after master recovery")
      if (d.desc.supervise) {
        logWarning(s"Re-launching ${d.id}")
        relaunchDriver(d)
      } else {
        removeDriver(d.id, DriverState.ERROR, None)
        logWarning(s"Did not re-launch ${d.id} because it was not supervised")
      }
    }

    state = RecoveryState.ALIVE
    schedule()
    logInfo("Recovery complete - resuming operations!")
  }

后续任务:
Master资源调度算法、worker主要工作等

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值