• Spark源码分析之三:Stage划分


    继上篇《Spark源码分析之Job的调度模型与运行反馈》之后,我们继续来看第二阶段--Stage划分。

            Stage划分的大体流程如下图所示:

            前面提到,对于JobSubmitted事件,我们通过调用DAGScheduler的handleJobSubmitted()方法来处理。那么我们先来看下代码:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // 处理Job提交的函数  
    2.   private[scheduler] def handleJobSubmitted(jobId: Int,  
    3.       finalRDD: RDD[_],  
    4.       func: (TaskContext, Iterator[_]) => _,  
    5.       partitions: Array[Int],  
    6.       callSite: CallSite,  
    7.       listener: JobListener,  
    8.       properties: Properties) {  
    9.     var finalStage: ResultStage = null  
    10.       
    11.     // 利用最后一个RDD(finalRDD),创建最后的stage对象:finalStage  
    12.     try {  
    13.       // New stage creation may throw an exception if, for example, jobs are run on a  
    14.       // HadoopRDD whose underlying HDFS files have been deleted.  
    15.       // 根据最后一个RDD获取最后的stage  
    16.       finalStage = newResultStage(finalRDD, func, partitions, jobId, callSite)  
    17.     } catch {  
    18.       case e: Exception =>  
    19.         logWarning("Creating new stage failed due to exception - job: " + jobId, e)  
    20.         listener.jobFailed(e)  
    21.         return  
    22.     }  
    23.   
    24.     // 创建一个ActiveJob对象  
    25.     val job = new ActiveJob(jobId, finalStage, callSite, listener, properties)  
    26.       
    27.     // 清除RDD分区位置缓存  
    28.     // private val cacheLocs = new HashMap[Int, IndexedSeq[Seq[TaskLocation]]]  
    29.     clearCacheLocs()  
    30.       
    31.     // 调用logInfo()方法记录日志信息  
    32.     logInfo("Got job %s (%s) with %d output partitions".format(  
    33.       job.jobId, callSite.shortForm, partitions.length))  
    34.     logInfo("Final stage: " + finalStage + " (" + finalStage.name + ")")  
    35.     logInfo("Parents of final stage: " + finalStage.parents)  
    36.     logInfo("Missing parents: " + getMissingParentStages(finalStage))  
    37.   
    38.     val jobSubmissionTime = clock.getTimeMillis()  
    39.       
    40.     // 将jobId-->ActiveJob的对应关系添加到HashMap类型的数据结构jobIdToActiveJob中去  
    41.     jobIdToActiveJob(jobId) = job  
    42.       
    43.     // 将ActiveJob添加到HashSet类型的数据结构activeJobs中去  
    44.     activeJobs += job  
    45.       
    46.     finalStage.setActiveJob(job)  
    47.       
    48.     //2 获取stageIds列表  
    49.     // jobIdToStageIds存储的是jobId--stageIds的对应关系  
    50.     // stageIds为HashSet[Int]类型的  
    51.     // jobIdToStageIds在上面newResultStage过程中已被处理  
    52.     val stageIds = jobIdToStageIds(jobId).toArray  
    53.     // stageIdToStage存储的是stageId-->Stage的对应关系  
    54.     val stageInfos = stageIds.flatMap(id => stageIdToStage.get(id).map(_.latestInfo))  
    55.       
    56.     listenerBus.post(  
    57.       SparkListenerJobStart(job.jobId, jobSubmissionTime, stageInfos, properties))  
    58.       
    59.     // 提交最后一个stage  
    60.     submitStage(finalStage)  
    61.   
    62.     // 提交其他正在等待的stage  
    63.     submitWaitingStages()  
    64.   }  

            这个handleJobSubmitted()方法一共做了这么几件事:

            第一,调用newResultStage()方法,生成Stage,包括最后一个Stage:ResultStage和前面的Parent Stage:ShuffleMapStage;

            第二,创建一个ActiveJob对象job;

            第三,清除RDD分区位置缓存;

            第四,调用logInfo()方法记录日志信息;

            第五,维护各种数据对应关系涉及到的数据结构:

            (1)将jobId-->ActiveJob的对应关系添加到HashMap类型的数据结构jobIdToActiveJob中去;

            (2)将ActiveJob添加到HashSet类型的数据结构activeJobs中去;

            第六,提交Stage;

            下面,除了提交Stage留在第三阶段外,我们挨个分析第二阶段的每一步。

            首先是调用newResultStage()方法,生成Stage,包括最后一个Stage:ResultStage和前面的Parent Stage:ShuffleMapStage。代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Create a ResultStage associated with the provided jobId. 
    3.    * 用提供的jobId创建一个ResultStage 
    4.    */  
    5.   private def newResultStage(  
    6.       rdd: RDD[_],  
    7.       func: (TaskContext, Iterator[_]) => _,  
    8.       partitions: Array[Int],  
    9.       jobId: Int,  
    10.       callSite: CallSite): ResultStage = {  
    11.       
    12.     // 根据fianl RDD获取parent stage及id,这个id为ResultStage的stageId  
    13.     val (parentStages: List[Stage], id: Int) = getParentStagesAndId(rdd, jobId)  
    14.       
    15.     // 创建一个ResultStage,即为整个Job的finalStage  
    16.     // 参数:id为stage的id,rdd为stage中最后一个rdd,func为在分区上执行的函数操作,  
    17.     // partitions为rdd中可以执行操作的分区,parentStages为该stage的父stages,jobId为该stage  
    18.     val stage = new ResultStage(id, rdd, func, partitions, parentStages, jobId, callSite)  
    19.       
    20.     // 将stage加入到stageIdToStage中  
    21.     stageIdToStage(id) = stage  
    22.       
    23.     // 更新数据结构jobIdToStageIds  
    24.     updateJobIdStageIdMaps(jobId, stage)  
    25.       
    26.     // 返回stage  
    27.     stage  
    28.   }  

            首先,根据fianl RDD获取parent stages及id,这个id为ResultStage的stageId;

            其次,创建一个ResultStage,即为整个Job的finalStage;

            然后,将stage加入到数据结构stageIdToStage中;

            接着,更新数据结构jobIdToStageIds;

            最后,返回这个ResultStage。

            我们一步步来看。首先调用getParentStagesAndId()方法,根据fianl RDD获取parent stages及id,这个id为ResultStage的stageId。代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Helper function to eliminate some code re-use when creating new stages. 
    3.    */  
    4.   private def getParentStagesAndId(rdd: RDD[_], firstJobId: Int): (List[Stage], Int) = {  
    5.     // 获取parent stages  
    6.     val parentStages = getParentStages(rdd, firstJobId)  
    7.       
    8.     // 获取下一个stageId,为AtomicInteger类型,getAndIncrement()能保证原子操作  
    9.     val id = nextStageId.getAndIncrement()  
    10.       
    11.     // 返回parentStages和id  
    12.     (parentStages, id)  
    13.   }  

            这个id即为下一个stageId,通过AtomicInteger类型的getAndIncrement()获得,能够保证原子性。继续分析getParentStages()方法,通过它来获取final RDD的parent stage。代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Get or create the list of parent stages for a given RDD.  The new Stages will be created with 
    3.    * the provided firstJobId. 
    4.    */  
    5.   private def getParentStages(rdd: RDD[_], firstJobId: Int): List[Stage] = {  
    6.     // 用HashSet存储parents stage  
    7.     val parents = new HashSet[Stage]  
    8.       
    9.     // 用HashSet存储已经被访问过的RDD  
    10.     val visited = new HashSet[RDD[_]]  
    11.       
    12.     // We are manually maintaining a stack here to prevent StackOverflowError  
    13.     // caused by recursively visiting  
    14.     // 存储需要被处理的RDD。Stack中得RDD都需要被处理  
    15.     val waitingForVisit = new Stack[RDD[_]]  
    16.       
    17.     // 定义一个visit函数,根据传入的RDD,如果之前没有处理过,标记为已处理,循环此RDD的依赖关系dependencies  
    18.     // 如果是ShuffleDependency,获取其parents;如果不是,则说明为同一stage,并压入Stack:waitingForVisit顶部  
    19.     def visit(r: RDD[_]) {  
    20.       if (!visited(r)) {// visited中没有的话  
    21.         // 将RDD r加入到visited,表示已经处理过了  
    22.         visited += r  
    23.           
    24.         // Kind of ugly: need to register RDDs with the cache here since  
    25.         // we can't do it in its constructor because # of partitions is unknown  
    26.         // 循环Rdd r的依赖关系  
    27.         for (dep <- r.dependencies) {  
    28.           dep match {  
    29.             case shufDep: ShuffleDependency[_, _, _] =>  
    30.               // 如果是ShuffleDependency,获取其parents,添加到parents中去  
    31.               parents += getShuffleMapStage(shufDep, firstJobId)  
    32.             case _ =>  
    33.               // 否则,属于同一个stage,压入Stack顶部,后续再递归处理  
    34.               waitingForVisit.push(dep.rdd)  
    35.           }  
    36.         }  
    37.       }  
    38.     }  
    39.       
    40.     // 将rdd压入Stack顶部  
    41.     waitingForVisit.push(rdd)  
    42.       
    43.     // 循环waitingForVisit,弹出每个rdd  
    44.     while (waitingForVisit.nonEmpty) {  
    45.       // 调用visit()方法,处理每个rdd  
    46.       visit(waitingForVisit.pop())  
    47.     }  
    48.       
    49.     // 返回得到的parents列表  
    50.     parents.toList  
    51.   }  

            getParentStages()方法在其内部定义了如下数据结构:

            parents:用HashSet存储parents stages,即finalRDD的所有parent stages,也就是ShuffleMapStage;

            visited:用HashSet存储已经被访问过的RDD,在RDD被处理前先存入该HashSet,保证存储在里面的RDD将不会被重复处理;

            waitingForVisit:存储需要被处理的RDD。Stack中得RDD都需要被处理。

            getParentStages()方法在其内部还定义了一个visit()方法,传入一个RDD,如果之前没有处理过,标记为已处理,并循环此RDD的依赖关系dependencies,如果是ShuffleDependency,调用getShuffleMapStage()方法获取其parent stage;如果不是,则说明为同一stage,并压入Stack:waitingForVisit顶部,等待后续通过visit()方法处理。所以,getParentStages()方法从finalRDD开始,逐渐往上查找,如果是窄依赖,证明在同一个Stage中,继续往上查找,如果是宽依赖,通过getShuffleMapStage()方法获取其parent stage,就能得到整个Job中所有的parent stages,也就是ShuffleMapStage。

            接下来,我们看下getShuffleMapStage()方法的实现。代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Get or create a shuffle map stage for the given shuffle dependency's map side. 
    3.    * 针对给定的shuffle dependency的map端,获取或者创建一个ShuffleMapStage 
    4.    */  
    5.   private def getShuffleMapStage(  
    6.       shuffleDep: ShuffleDependency[_, _, _],  
    7.       firstJobId: Int): ShuffleMapStage = {  
    8.       
    9.     // 从数据结构shuffleToMapStage中根据shuffleId获取,如果有直接返回,否则  
    10.     // 获取ShuffleDependency中的rdd,调用getAncestorShuffleDependencies()方法,  
    11.     // 循环每个parent,调用newOrUsedShuffleStage()方法,创建一个新的ShuffleMapStage,  
    12.     // 并加入到数据结构shuffleToMapStage中去  
    13.     //   
    14.     // 它的定义为:private[scheduler] val shuffleToMapStage = new HashMap[Int, ShuffleMapStage]  
    15.     shuffleToMapStage.get(shuffleDep.shuffleId) match {  
    16.       case Some(stage) => stage // 有则直接返回  
    17.       case None => // 没有  
    18.         // We are going to register ancestor shuffle dependencies  
    19.         // 调用getAncestorShuffleDependencies()方法,传入ShuffleDependency中的rdd  
    20.           
    21.         // 发现还没有在shuffleToMapStage中注册的祖先shuffle dependencies  
    22.         getAncestorShuffleDependencies(shuffleDep.rdd).foreach { dep =>  
    23.           // 并循环返回的parents,调用newOrUsedShuffleStage()方法,创建一个新的ShuffleMapStage,  
    24.           // 并加入到数据结构shuffleToMapStage中去  
    25.           shuffleToMapStage(dep.shuffleId) = newOrUsedShuffleStage(dep, firstJobId)  
    26.         }  
    27.           
    28.         // Then register current shuffleDep  
    29.         // 最后注册当前shuffleDep,并加入到数据结构shuffleToMapStage中,返回stage  
    30.         val stage = newOrUsedShuffleStage(shuffleDep, firstJobId)  
    31.         shuffleToMapStage(shuffleDep.shuffleId) = stage  
    32.         stage  
    33.     }  
    34.   }  

            从getShuffleMapStage()方法的注释就能看出,这个方法的主要作用就是针对给定的shuffle dependency的map端,获取或者创建一个ShuffleMapStage。为何是Get or create呢?通过源码得知,getShuffleMapStage()方法首先会根据shuffleDep.shuffleId从数据结构shuffleToMapStage中查找哦是否存在对应的stage,如果存在则直接返回,如果不存在,则调用newOrUsedShuffleStage()方法创建一个Stage并添加到数据结构shuffleToMapStage中,方便后续需要使用此Stage者直接使用。在此之前,会根据入参ShuffleDependency的rdd发现还没有在shuffleToMapStage中注册的祖先shuffle dependencies,然后遍历每个ShuffleDependency,调用newOrUsedShuffleStage()方法为每个ShuffleDependency产生Stage并添加到数据结构shuffleToMapStage中。

            下面,我们看下这个getAncestorShuffleDependencies()方法的实现,代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** Find ancestor shuffle dependencies that are not registered in shuffleToMapStage yet */  
    2.   // 根据传入的RDD,发现还没有在shuffleToMapStage中未注册过的祖先shuffle dependencies  
    3.   private def getAncestorShuffleDependencies(rdd: RDD[_]): Stack[ShuffleDependency[_, _, _]] = {  
    4.       
    5.     // 存放parents的栈:Stack  
    6.     val parents = new Stack[ShuffleDependency[_, _, _]]  
    7.       
    8.     // 存放已经处理过的RDD的哈希表:HashSet  
    9.     val visited = new HashSet[RDD[_]]  
    10.       
    11.     // We are manually maintaining a stack here to prevent StackOverflowError  
    12.     // caused by recursively visiting  
    13.     // 存放等待调用visit的RDD的栈:Stack  
    14.     val waitingForVisit = new Stack[RDD[_]]  
    15.       
    16.     // 定义方法visit()  
    17.     def visit(r: RDD[_]) {  
    18.       if (!visited(r)) {// 如果之前没有处理过  
    19.         visited += r // 标记为已处理  
    20.           
    21.         // 循环RDD的所有依赖  
    22.         for (dep <- r.dependencies) {  
    23.           dep match {  
    24.             case shufDep: ShuffleDependency[_, _, _] => // 如果是ShuffleDependency  
    25.               // 如果shuffleToMapStage中没有,添加到parents中  
    26.               if (!shuffleToMapStage.contains(shufDep.shuffleId)) {  
    27.                 parents.push(shufDep)  
    28.               }  
    29.             case _ =>  
    30.           }  
    31.             
    32.           // 将该dependence的rdd压入waitingForVisit栈顶部  
    33.           waitingForVisit.push(dep.rdd)  
    34.         }  
    35.       }  
    36.     }  
    37.   
    38.     // 将RDD压入到waitingForVisit顶部  
    39.     waitingForVisit.push(rdd)  
    40.     // 循环waitingForVisit,针对每个RDD调用visit()方法  
    41.     while (waitingForVisit.nonEmpty) {  
    42.       visit(waitingForVisit.pop())  
    43.     }  
    44.       
    45.     // 返回parents  
    46.     parents  
    47.   }  

            通过代码我们可以发现,它和getParentStages()方法的代码风格非常相似。在其内部也定义了三个数据结构:

            parents:存放parents的栈,即Stack,用于存放入参RDD的在shuffleToMapStage中未注册过的祖先shuffle dependencies;

            visited:存放已经处理过的RDD的哈希表,即HashSet;

            waitingForVisit:存放等待被处理的RDD的栈,即Stack;

            定义了一个visit()方法,入参为RDD,针对传入的RDD,如果之前没有处理过则标记为已处理,并循环RDD的所有依赖,如果是如果是ShuffleDependency,并且其依赖的shuffleId在shuffleToMapStage中没有,添加到parents中,否则直接跳过,最后无论为何种Dependency,都将该dependence的rdd压入waitingForVisit栈顶部,等待后续处理。

            接下来,我们再看下newOrUsedShuffleStage()方法,其代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Create a shuffle map Stage for the given RDD.  The stage will also be associated with the 
    3.    * provided firstJobId.  If a stage for the shuffleId existed previously so that the shuffleId is 
    4.    * present in the MapOutputTracker, then the number and location of available outputs are 
    5.    * recovered from the MapOutputTracker 
    6.    * 
    7.    * 为给定的RDD创建一个ShuffleStage 
    8.    */  
    9.   private def newOrUsedShuffleStage(  
    10.       shuffleDep: ShuffleDependency[_, _, _],  
    11.       firstJobId: Int): ShuffleMapStage = {  
    12.       
    13.     // 从shuffleDep中获取RDD   
    14.     val rdd = shuffleDep.rdd  
    15.       
    16.     // 获取RDD的分区个数,即未来的task数目  
    17.     val numTasks = rdd.partitions.length  
    18.       
    19.     // 构造一个ShuffleMapStage实例  
    20.     val stage = newShuffleMapStage(rdd, numTasks, shuffleDep, firstJobId, rdd.creationSite)  
    21.       
    22.       
    23.     if (mapOutputTracker.containsShuffle(shuffleDep.shuffleId)) {  
    24.       // 如果mapOutputTracker中存在  
    25.       
    26.       // 根据shuffleId从mapOutputTracker中获取序列化的多个MapOutputStatus对象  
    27.       val serLocs = mapOutputTracker.getSerializedMapOutputStatuses(shuffleDep.shuffleId)  
    28.         
    29.       // 反序列化  
    30.       val locs = MapOutputTracker.deserializeMapStatuses(serLocs)  
    31.         
    32.       // 循环  
    33.       (0 until locs.length).foreach { i =>  
    34.         if (locs(i) ne null) {  
    35.           // locs(i) will be null if missing  
    36.           // 将  
    37.           stage.addOutputLoc(i, locs(i))  
    38.         }  
    39.       }  
    40.     } else {  
    41.       // 如果mapOutputTracker中不存在,注册一个  
    42.       
    43.       // Kind of ugly: need to register RDDs with the cache and map output tracker here  
    44.       // since we can't do it in the RDD constructor because # of partitions is unknown  
    45.       logInfo("Registering RDD " + rdd.id + " (" + rdd.getCreationSite + ")")  
    46.       // 注册的内容为  
    47.       // 1、根据shuffleDep获取的shuffleId;  
    48.       // 2、rdd中分区的个数  
    49.       mapOutputTracker.registerShuffle(shuffleDep.shuffleId, rdd.partitions.length)  
    50.     }  
    51.     stage  
    52.   }  

            这个方法的主要完成了以下两件事:

            1、构造一个ShuffleMapStage实例stage;

            2、判断是否在mapOutputTracker中存在:

               (1)如果不存在,调用mapOutputTracker的registerShuffle()方法注册一个,注册的内容为根据shuffleDep获取的shuffleId和rdd中分区的个数;

               (2)如果存在,根据shuffleId从mapOutputTracker中获取序列化的多个MapOutputStatus对象,反序列化后循环,逐个添加到stage中。

            紧接着,看下newShuffleMapStage()方法,其代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Create a ShuffleMapStage as part of the (re)-creation of a shuffle map stage in 
    3.    * newOrUsedShuffleStage.  The stage will be associated with the provided firstJobId. 
    4.    * Production of shuffle map stages should always use newOrUsedShuffleStage, not 
    5.    * newShuffleMapStage directly. 
    6.    */  
    7.   private def newShuffleMapStage(  
    8.       rdd: RDD[_],  
    9.       numTasks: Int,  
    10.       shuffleDep: ShuffleDependency[_, _, _],  
    11.       firstJobId: Int,  
    12.       callSite: CallSite): ShuffleMapStage = {  
    13.       
    14.     // 获得parentStages和下一个stageId  
    15.     val (parentStages: List[Stage], id: Int) = getParentStagesAndId(rdd, firstJobId)  
    16.       
    17.     // 创建一个ShuffleMapStage  
    18.     val stage: ShuffleMapStage = new ShuffleMapStage(id, rdd, numTasks, parentStages,  
    19.       firstJobId, callSite, shuffleDep)  
    20.   
    21.     // 将stage加入到数据结构stageIdToStage  
    22.     stageIdToStage(id) = stage  
    23.     updateJobIdStageIdMaps(firstJobId, stage)  
    24.     stage  
    25.   }  

            可以发现,这个方法也调用了getParentStagesAndId()方法,这样,就形成了一个递归,按照RDD的依赖关系,由后往前,逐渐生成Stage。代码剩余的部分就是创建一个ShuffleMapStage,并将stage加入到数据结构stageIdToStage,以及调用updateJobIdStageIdMaps()方法更新相关数据结构。这个updateJobIdStageIdMaps()方法留待下面分析。

            下面,简单看下mapOutputTracker注册的代码。

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. // 注册shuffle  
    2.   def registerShuffle(shuffleId: Int, numMaps: Int) {  
    3.     // 将shuffleId、numMaps大小和MapStatus类型的Array数组的映射关系,放入mapStatuses中  
    4.     // mapStatuses为TimeStampedHashMap[Int, Array[MapStatus]]类型的数据结构  
    5.     if (mapStatuses.put(shuffleId, new Array[MapStatus](numMaps)).isDefined) {  
    6.       throw new IllegalArgumentException("Shuffle ID " + shuffleId + " registered twice")  
    7.     }  
    8.   }  

            很简单,将shuffleId、numMaps大小和MapStatus类型的Array数组的映射关系,放入mapStatuses中,mapStatuses为TimeStampedHashMap[Int, Array[MapStatus]]类型的数据结构。
           经历了这多又长又大篇幅的叙述,现在返回newResultStage()方法,在通过getParentStagesAndId()方法获取parent stages及其result stage的id后,紧接着创建一个ResultStage,并将stage加入到stageIdToStage中,最后在调用updateJobIdStageIdMaps()更新数据结构jobIdToStageIds后,返回stage。

            下面,简单看下updateJobIdStageIdMaps()方法。代码如下:

    [java] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /** 
    2.    * Registers the given jobId among the jobs that need the given stage and 
    3.    * all of that stage's ancestors. 
    4.    */  
    5.   private def updateJobIdStageIdMaps(jobId: Int, stage: Stage): Unit = {  
    6.     // 定义一个函数updateJobIdStageIdMapsList()  
    7.     def updateJobIdStageIdMapsList(stages: List[Stage]) {  
    8.         
    9.       if (stages.nonEmpty) {  
    10.           
    11.         // 获取列表头元素  
    12.         val s = stages.head  
    13.           
    14.         // 将jobId添加到Stage的jobIds中  
    15.         s.jobIds += jobId  
    16.           
    17.         // 更新jobIdToStageIds,将jobId与stageIds的对应关系添加进去  
    18.         jobIdToStageIds.getOrElseUpdate(jobId, new HashSet[Int]()) += s.id  
    19.           
    20.         val parents: List[Stage] = getParentStages(s.rdd, jobId)  
    21.           
    22.         val parentsWithoutThisJobId = parents.filter { ! _.jobIds.contains(jobId) }  
    23.         updateJobIdStageIdMapsList(parentsWithoutThisJobId ++ stages.tail)  
    24.       }  
    25.     }  
    26.     // 调用函数updateJobIdStageIdMapsList()  
    27.     updateJobIdStageIdMapsList(List(stage))  
    28.   }  

            这个方法的实现比较简单,在其内部定义了一个函数updateJobIdStageIdMapsList(),首选传入result stage,将jobId添加到stage的jobIds中,更新jobIdToStageIds,将jobId与stageIds的对应关系添加进去,然后根据给定stage的RDD获取其parent stages,过滤出不包含此JobId的parents stages,再递归调用updateJobIdStageIdMapsList()方法,直到全部stage都处理完。

            至此,第二阶段Stage划分大体流程已分析完毕,有遗漏或不清楚的地方,以后再查缺补漏以及细化及更正错误。

    博客原地址:http://blog.csdn.net/lipeng_bigdata/article/details/50674189

  • 相关阅读:
    SQL中的union
    SQL的类型转换
    Keytool生成证书
    Openssl生成证书
    Python示例-Json Parse
    Python示例-TCP Port Scan
    Python套接字
    TCP端口扫描
    Linux环境变量
    Python示例-Logging
  • 原文地址:https://www.cnblogs.com/jirimutu01/p/5274456.html
Copyright © 2020-2023  润新知