Spark调度机制:4)阶段划分

阶段划分是作业调度过程的关键所在,首先探讨下Spark是如何进行阶段划分的。

一个阶段划分的例子如下图所示,用虚线表示一个阶段,虚线框内所有的RDD都是为了实现该阶段而需要被计算的数据。整个作业最后一个RDD的所有分区数据被计算完毕对于的阶段就是所求的末阶段。

沿着RDD的依赖关系往前进行深度优先遍历,若遇到一个Shuffle依赖,依赖的每一个父RDD所有分区数据都计算完毕可以分别对应一个阶段,且都是当前阶段的父阶段,继续沿着父RDD往前遍历,若遇到一个窄依赖,则直接往前遍历,直到当前所有的依赖关系都被遍历才返回上一层,通过这个过程,最后会得到一张DAG。DAG的最终阶段称之为结果阶段(Result Stage),其余的阶段称为ShuffleMap阶段。(简单区分窄依赖和Shuffle依赖,看父RDD是否存在一个分区有大于1条线出去,若有则为Shuffle依赖)


以上图为例,Stage3是结果阶段,沿着RDD的依赖关系,从G向前遍历。(明确一点Spark阶段划分是包含式的)

首先看A->B->G这条路径,B->G是窄依赖,继续向前到A->B是Shuffle依赖,B的父RDD-A所有分区数据被计算完成可以视为一个阶段,所以RDD_A可以视为一个阶段Stage1。

再看C->D->F->G这条路径,F->G是shuffle依赖,G的父RDD-F所有分区数据被计算完成可以视为一个阶段,也就是下面的整体。由于其他的路径都是窄依赖,因此只有一个阶段Stage2。

如上图所示,将Shuffle依赖作为两个阶段的分割点,并记录二者之间的阶段依赖关系,这部分的功能在newResultStage方法中实现

  private def newResultStage(
      rdd: RDD[_],
      func: (TaskContext, Iterator[_]) => _,
      partitions: Array[Int],
      jobId: Int,
      callSite: CallSite): ResultStage = {
    val (parentStages: List[Stage], id: Int) = getParentStagesAndId(rdd, jobId)
    val stage = new ResultStage(id, rdd, func, partitions, parentStages, jobId, callSite)
    stageIdToStage(id) = stage
    updateJobIdStageIdMaps(jobId, stage)
    stage
  }
可以看到,newResultStage函数内部先调用 getParentStagesAndId获得父辈阶段集合parentStages和阶段唯一标识ID,parentStages中的每一个阶段又保存了与其父辈阶段的关系

  private def getParentStagesAndId(rdd: RDD[_], firstJobId: Int): (List[Stage], Int) = {
    val parentStages = getParentStages(rdd, firstJobId)
    val id = nextStageId.getAndIncrement()
    (parentStages, id)
  }
getParentStages是一个比较复杂的堆栈递归过程,对于每一个阶段的父阶段,都会将其封装成一个Stage对象,并添加到parentStages中。换句话说,parentStages得到的实际上就是除了当前阶段在内的DAG图。

   //递归构建DAG图,结果保存在parents中
  private def getParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {
    val parents = 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(r: RDD[_]) {
      if (!visited(r)) {
        visited += r
        // Kind of ugly: need to register RDDs with the cache here since
        // we can't do it in its constructor because # of partitions is unknown
        for (dep <- r.dependencies) {
          dep match {
            case shufDep: ShuffleDependency[_, _, _] =>
              parents += getShuffleMapStage(shufDep, firstJobId)
            case _ =>
              waitingForVisit.push(dep.rdd)
          }
        }
      }
    }
    waitingForVisit.push(rdd)
    while (waitingForVisit.nonEmpty) {
      visit(waitingForVisit.pop())
    }
    parents.toList
  }

至此,DAG调度已经完成了阶段划分的工作,并把任务集交付给任务调度器,具体可参看下一章节:Spark调度机制:5)任务调度

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值