Spark stage提交

本文深入解析Spark中Stage的提交流程,包括作业提交事件处理、DAGScheduler如何处理作业提交、submitStage方法的工作原理以及如何查找并处理上级Stage,是Spark开发者理解任务调度不可或缺的资源。

Spark stage提交

更多资源

Youtube 视频

BiliBili 视频

src="//player.bilibili.com/player.html?aid=37445077&page=1" scrolling="no" border="0" allowfullscreen="true">

作业提交事件处理

  • DAGScheduler 处事作业提交事件
  • 用参数finalStage 调用 submitStage() 方法
 private[scheduler] def handleJobSubmitted(jobId: Int,
      finalRDD: RDD[_],
      func: (TaskContext, Iterator[_]) => _,
      partitions: Array[Int],
      callSite: CallSite,
      listener: JobListener,
      properties: Properties) {
    var finalStage: ResultStage = null
    try {
      // New stage creation may throw an exception if, for example, jobs are run on a
      // HadoopRDD whose underlying HDFS files have been deleted.
      finalStage = newResultStage(finalRDD, func, partitions, jobId, callSite)
    } catch {
      case e: Exception =>
        logWarning("Creating new stage failed due to exception - job: " + jobId, e)
        listener.jobFailed(e)
        return
    }

    val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)
    clearCacheLocs()
    logInfo("Got job %s (%s) with %d output partitions".format(
      job.jobId, callSite.shortForm, partitions.length))
    logInfo("Final stage: " + finalStage + " (" + finalStage.name + ")")
    logInfo("Parents of final stage: " + finalStage.parents)
    logInfo("Missing parents: " + getMissingParentStages(finalStage))

    val jobSubmissionTime = clock.getTimeMillis()
    jobIdToActiveJob(jobId) = job
    activeJobs += job
    finalStage.setActiveJob(job)
    val stageIds = jobIdToStageIds(jobId).toArray
    val stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))
    listenerBus.post(
      SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))
    submitStage(finalStage)

    submitWaitingStages()
  }

submitStage 方法处理

  • 验证当前stage所在的job是活动的才继续(如Job取消了,再继续也没有意义)
  • 对waitingStages,runningStages,failedStages 进行验证(stage不能重复提交)
  • stage提交之前先验证当前stage的上级stage是否为空,只有为空的才可以提交
  • 当ShuffleMapStage的所有partition处理完成后,会设置isAvailable为真,也就是该stage已被处理完成,不需要再处理了,这时他的子Stage就可以提交了
/** Submits stage, but first recursively submits any missing parents. */
  private def submitStage(stage: Stage) {
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug("submitStage(" + stage + ")")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
        val missing = getMissingParentStages(stage).sortBy(_.id)
        logDebug("missing: " + missing)
        if (missing.isEmpty) {
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          submitMissingTasks(stage, jobId.get)
        } else {
          for (parent <- missing) {
            submitStage(parent)
          }
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id, None)
    }
  }
  • 查找上级Stage
  • 内部会递归一直找到祖先Stage
  • (在这里判断)当ShuffleMapStage的所有partition处理完成后,会设置isAvailable为真,也就是该stage已被处理完成,不需要再处理了,这时他的子Stage就可以提交了
  • getShuffleMapStage 跟 FinalStage的构建,那时的Stage划分一样,并且在FinalStage已对ShuffleDenpendency的Stage进行了缓存,这时直接根据ShuffleId匹配,直接用
private def getMissingParentStages(stage: Stage): List[Stage] = {
    val missing = new HashSet[Stage]
    val visited = new HashSet[RDD[_]]
    // We are manually maintaining a stack here to prevent StackOverflowError
    // caused by recursively visiting
    val waitingForVisit = new Stack[RDD[_]]
    def visit(rdd: RDD[_]) {
      if (!visited(rdd)) {
        visited += rdd
        val rddHasUncachedPartitions = getCacheLocs(rdd).contains(Nil)
        if (rddHasUncachedPartitions) {
          for (dep <- rdd.dependencies) {
            dep match {
              case shufDep: ShuffleDependency[_, _, _] =>
                val mapStage = getShuffleMapStage(shufDep, stage.firstJobId)
                if (!mapStage.isAvailable) {
                  missing += mapStage
                }
              case narrowDep: NarrowDependency[_] =>
                waitingForVisit.push(narrowDep.rdd)
            }
          }
        }
      }
    }
    waitingForVisit.push(stage.rdd)
    while (waitingForVisit.nonEmpty) {
      visit(waitingForVisit.pop())
    }
    missing.toList
  }
### Apache Spark Stage 划分源码分析 #### 程序入口与初始化 当执行 `spark-submit` 命令时,实际调用的是位于 `${SPARK_HOME}/bin/` 下的 `spark-class` 脚本。此脚本最终会启动 `org.apache.spark.deploy.SparkSubmit` 类来处理命令行参数并准备应用程序环境[^1]。 ```bash exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@" ``` #### DAGScheduler 和 Stage 划分逻辑 DAGScheduler 是负责管理作业调度的核心组件之一,在接收到由驱动器端发起的动作操作(action)请求之后,DAGScheduler 将构建有向无环图(Directed Acyclic Graph, DAG),并将该图形转换成一系列的任务集(TaskSet)[^2]。具体来说: - **Job 提交**: 当用户提交一个 Action 操作给 Spark 应用程序时,这将触发一个新的 Job 创建。 - **Stage 构建**: 对于每一个新创建的 Job,DAGScheduler 需要解析构成这个 Job 的所有宽依赖关系(Wide Dependency)和窄依赖关系(Narrow Dependency), 并据此定义多个 Stage。每个 Stage 内部只包含具有窄依赖的数据流变换;而不同 Stages 之间则存在宽依赖边界[^3]。 - **Parent RDDs 获取**: 在确定如何分割这些 Stage 后,DAGScheduler 使用 `RDD.getDependencies()` 方法获取当前 RDD 所有的父级 RDD 实例列表作为依据来进行进一步划分[^4]。 #### 结果计算阶段( Result Stage ) 对于那些直接对应于某些特定动作的操作而言,比如 `count()`, `saveAsTextFile()` 或者其他任何返回值类型的 API ,它们会被视为特殊的 Stage 称之为 “Result Stage”。这类 Stage 特指那部分能够立即产出最终结果数据集合的工作单元。 ```scala // 示例代码片段展示了一个简单的Action操作是如何被转化为ResultStage的. val rdd = sc.parallelize(Seq(("a", 1), ("b", 2))) rdd.count() ``` 上述例子中,`count()` 动作将会促使系统生成相应的 ResultStage 来完成计数统计工作。 #### 完整流程总结 综上所述,整个 Stage 划分过程大致如下: - 用户通过 `spark-submit` 发起应用; - SparkSubmit 解析配置项并向集群申请资源; - Driver 接收到来自 Client 的指令后开始构建 DAG 图形结构; - 根据不同的 ShuffleDependency 关系切割出独立运作的小型任务组即 Stage; - 如果遇到 Action 类型的操作,则额外形成专门用于求解终态数据的结果 Stage
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值