spark调度系列------4. RDD依赖的建立以及RDD依赖在任务提交到调度系统的作用

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u012684933/article/details/48714915

     Spark中RDD依赖的类关系如下图:



RDD Dependency的创建在RDD的getDependency方法,比如说ShuffledRDD.getDependency方法的定义:

override def getDependencies: Seq[Dependency[_]] = {
    List(new ShuffleDependency(prev, part, serializer, keyOrdering, aggregator, mapSideCombine))
  }

一个RDD跟它的parent RDD的依赖关系,在外部一般是通过调用RDD.dependencies方法获得的,定义如下:

final def dependencies: Seq[Dependency[_]] = {
    checkpointRDD.map(r => List(new OneToOneDependency(r))).getOrElse {
      if (dependencies_ == null) {
        dependencies_ = getDependencies
      }
      dependencies_
    }
  }


RDD依赖的UML类图如上图所示,分NarrowDependency和ShuffleDependency两种。

1. NarrowDependency:一个Parent RDD的数据经过处理之后,只能传递到一个Child RDD,它又分3中类型

                a. OneToOneDependency:MapPartitionsRDD、HadoopRDD的依赖,这种依赖Parent RDD和Child RDD是1对1的关系

b. RangeDependency:UnionRDD的依赖,这种依赖是多个Parent RDD对应1个Child RDD

c. PruneDependency: PartitionPruningRDD的依赖,目前还没有接触过,后继补充

2. ShuffleDependency:一个Parent RDD的数据经过处理之后,传递给多个Child RDD,ShuffledRDD的依赖就是这种类型。通过上图可以看到ShuffleDependency比较复杂,这些数据都是控制Shuffle的关键变量,这些变量在后继讲Shuffle的时候会详细说明

RDD和Dependency之间的关系可以通过如下图说明:



通过调用RDD.dependencies方法,可以获得这个RDD对它所有Parent RDD的依赖List(比如说在UnionRDD情况下,它有多个Parent RDD,调用它的Dependencies方法会返回一个依赖List),依赖List的每个元素是一个Dependency,Dependency.rdd方法可以获得这个RDD的Parent RDD。Root RDD的Dependencies会返回Nil


DAGScheduler根据上面的RDD的依赖关系进行任务分解,在这里RDD的依赖关系主要起到了2个作用:

1. 根据RDD的依赖关系,将所有需要Shuffle的Stage加入到DAGScheduler.shuffleToMapStage这个HashMap中,HashMap的Key是shuffleId,Value是Stage

2. 根据RDD的依赖关系,从DAGScheduler.shuffleToMapStage这个HashMap中取出Stage,将每个Stage分解成TaskSet,然后将Task中的任务加入到等待Task执行的HashMap

下面来具体分析下是如何根据RDD依赖进行任务分解的。

DAGScheduler.submitStage方法负责任务的分解,代码如下:

 /** Submits stage, but first recursively submits any missing parents. */
  /*
  * 把Application的所有Stage全部分解成Task,提交到等待任务执行的HashMap
  * */
  private def submitStage(stage: Stage) {
    val jobId = activeJobForStage(stage)
    if (jobId.isDefined) {
      logDebug("submitStage(" + stage + ")")
      if (!waitingStages(stage) && !runningStages(stage) && !failedStages(stage)) {
        /*
        * 根据shuffleId进行排序,ShuffleId小的Stage排在前面,ShuffleId小的Stage会优先提交任务
        * */
        val missing = getMissingParentStages(stage).sortBy(_.id)
        logDebug("missing: " + missing)
        /*
         *root stage没有parent stage,missing.isEmpty==true 提交任务
         * 假设一个rdd依赖2个rdd(比如UnionRDD),而依赖的2个rdd又都是shuffedRDD,RDD1加入missing链表,RDD2后加入missing链表,
         * 由于是递归操作,会深度优先计算,先提交RDD1的所有祖宗Task,再提交RDD2的祖宗Task
         * RDD
         * */
        if (missing.isEmpty) {
          logInfo("Submitting " + stage + " (" + stage.rdd + "), which has no missing parents")
          /*
          * 将没有依赖Stage或者Parent Stage已经执行完成的Task提交到Task等待执行HashMap
          * */
          submitMissingTasks(stage, jobId.get)
        } else {
          for (parent <- missing) {
            /*
            * 递归调用,继续找当前Stage的Parent Stage
            * */
            submitStage(parent)
          }
          waitingStages += stage
        }
      }
    } else {
      abortStage(stage, "No active job for stage " + stage.id)
    }
  }

DAGScheduler.submitStage方法是一个递归调用,采用了深度优先算法提交依赖Stage的任务到任务等待HashMap中。请注意看上面DAGScheduler.submitStage方法的注解。

RDD依赖关系的利用在DAGScheduler.getMissingParentStages方法:

 //找到当前stage的所有parent stage,找到parent stage之后不再继续查找更上一级的祖宗stage
  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) {
          //根据RDD的依赖关系切分Stage
          for (dep <- rdd.dependencies) {
            dep match {
              case shufDep: ShuffleDependency[_, _, _] =>
                /*找到parent stage之后,不再继续将当前的rdd加入到waitingForVisit栈中
                *这个方法会把shufDepp依赖链的所有祖宗Stage加入到DAGScheduler.shuffleToMapStage HashMap中
                 */
                val mapStage = getShuffleMapStage(shufDep, stage.firstJobId)
                /*
                *什么时候返回true需要接下来研究,一个stage的parent stage的task提交之后,这个方法可能返回true,
                * 导致missing为empty返回,然后这个Stage的task在submitStage方法就可以提交了
                * */
                if (!mapStage.isAvailable) {
                  missing += mapStage
                }
              case narrowDep: NarrowDependency[_] =>
                //没有找到parent stage,将当前的rdd加入到waitingForVisit栈中
                waitingForVisit.push(narrowDep.rdd)
            }
          }
        }
      }
    }
    waitingForVisit.push(stage.rdd)
    while (waitingForVisit.nonEmpty) {
      /*
      * 从waitingForVisit栈弹出RDD,继续依赖分析,切割Stage
      * */
      visit(waitingForVisit.pop())
    }
    missing.toList
  }


在这个方法里面调用了rdd.dependencies方法切分Stage,是否需要分割Stage是根据是否是ShufleDependency,如果是ShuffleDependency则切分Stage。否则将当前RDD的Parent RDD加入到waitinfForVisit中,进行后继依赖分析查找切分Stage。请注意看上面代码的注解。

在这个方法也调用了DAGScheduler.getShuffleMapStage方法,将将所有需要Shuffle的Stage加入到DAGScheduler.shuffleToMapStage这个HashMap中,代码如下:

  private def getShuffleMapStage(
      shuffleDep: ShuffleDependency[_, _, _],
      firstJobId: Int): ShuffleMapStage = {
    shuffleToMapStage.get(shuffleDep.shuffleId) match {
      case Some(stage) => stage
      case None =>
        // We are going to register ancestor shuffle dependencies
        //在这个方法会把shuffleDep依赖链的所有祖宗Stage加入到shuffleToMapStage,但是当前Stage没有加入
        registerShuffleDependencies(shuffleDep, firstJobId)
        // Then register current shuffleDep
        val stage = newOrUsedShuffleStage(shuffleDep, firstJobId)
        //把当前Stage加入到shuffleToMapStage
        shuffleToMapStage(shuffleDep.shuffleId) = stage

        stage
    }
  }

这个方法根据shuffleDep.shuffleId查找Stage,如果Stage不存在则创建Stage,并且将新创建的Stage加入到DAGScheduler.shuffleToMapStage。这个方法调用了registerShuffleDependencies方法,它把shuffleDep依赖链的所有祖宗Stage加入到了shuffleToMapStage,代码如下:

private def registerShuffleDependencies(shuffleDep: ShuffleDependency[_, _, _], firstJobId: Int) {
    val parentsWithNoMapStage = getAncestorShuffleDependencies(shuffleDep.rdd)
    while (parentsWithNoMapStage.nonEmpty) {
      val currentShufDep = parentsWithNoMapStage.pop()
      //根据ShuffleDependency和jobid生成Stage,由于是从栈里面弹出,所以最先添加的是Root stage,依次类推,最先添加的Stage shuffleId越小
      val stage = newOrUsedShuffleStage(currentShufDep, firstJobId)
      shuffleToMapStage(currentShufDep.shuffleId) = stage
    }
  }

  /** Find ancestor shuffle dependencies that are not registered in shuffleToMapStage yet */
  private def getAncestorShuffleDependencies(rdd: RDD[_]): Stack[ShuffleDependency[_, _, _]] = {
    val parents = new Stack[ShuffleDependency[_, _, _]]
    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
        for (dep <- r.dependencies) {
          dep match {
            case shufDep: ShuffleDependency[_, _, _] =>
              if (!shuffleToMapStage.contains(shufDep.shuffleId)) {
                //将没有放在shuffleToMapStage的shuffleDependency放到栈中,等待返回
                parents.push(shufDep)
              }
              /*
              * 假设一个rdd依赖2个rdd(比如UnionRDD),而依赖的2个rdd又都是shuffedRDD,RDD1先入栈,RDD2后入栈,由于是栈操作,会深度优先计算,先计算RDD2
              * 的所有祖宗ShuffDep同时加入到parents这个栈之中
              * */
              waitingForVisit.push(shufDep.rdd)
            case _ =>
              waitingForVisit.push(dep.rdd)
          }
        }
      }
    }

    waitingForVisit.push(rdd)
    while (waitingForVisit.nonEmpty) {
      visit(waitingForVisit.pop())
    }
    parents
  }
请注意看上面代码的注解

getAncestorShuffleDependencies方法调用RDD.dependencies方法,获得RDD的依赖关系,如果跟它的parent RDD是ShuffleDependency依赖关系,则把这个ShuffleDependency加入到parents栈中,否则将当前RDD的parent RDD加入到waitingForVisit栈中,等待下次调用visit方法继续分析。

这样使用getAncestorShuffleDependencies方法把当前RDD的所有祖宗ShuffledRDD的依赖关系加入到parents栈中,然后返回。registerShuffleDependencies根据这个parents依赖栈,将当前RDD的所有祖宗Stage加入到DAGScheduler.shuffleToMapStage这个HashMap中




























展开阅读全文

没有更多推荐了,返回首页