Stage划分源码分析

概述

stage 划分思想
  由submitStage() 和getMissingParentStage() 组成。会从触发Action操作的那个RDD开始往前,首先为最后一个RDD创建一个stage,然后再往前,如果遇到某个RDD是宽依赖,就会为宽依赖创建一个新的stage,新的RDD就是最新的stage的最后一个RDD。然后依次类推,继续往前,根据宽依赖或者窄依赖进行stage划分,直到最后一个RDD遍历完为止

窄依赖:父RDD和子RDDpartition之间是一对一或多对一
宽依赖:父RDD和子RDD partition之间是一对多
  窄依赖一般是对RDD进行map,filter,union等Transformations。宽依赖一般是对RDD进行groupByKey,reduceByKey等操作,就是对RDD中的partition中的数据进行重分区(shffle)。join操作既可能是宽依赖也可能是窄依赖,当要对RDD进行join操作时,如果RDD进行过重分区则为宽依赖,否则为窄依赖。

为什么要根据宽依赖划分stage
  窄依赖会被划分到同一个Stage中,这样就能以管道的方式迭代执行。宽依赖由于依赖的上游RDD,需要首先计算好所有父分区数据,然后在节点间进行shuffle。从容灾角度讲,它们恢复计算结果的方式不同。窄依赖只需要重新执行父RDD的丢失分区的计算即可恢复。而宽依赖则需要考虑恢复所有父RDD的丢失分区并且同一RDD下的其他分区数据也重新计算了一次。

提交stage的方法(stage划分算法入口):
  调用 getMissingParentStage() 获取当前这个 stage 的父 stage;往栈中推入stage的最后一个RDD;while循环对stage的最后一个RDD,调用visit()方法:如果是窄依赖,将RDD放入栈中,如果是宽依赖,使用宽依赖的那个RDD创建一个stage,将isShuffleMap设为true;提交stage,为stage创建一批task,task数量与Partition数量相同;计算每个task对应的Partition的最佳位置(从stage最后一个RDD开始,去找被cache或checkpoint的RDD的Partition,task的最佳位置,就是该Partition的位置,这样task就在那个节点上执行,不需要计算之前的RDD;如果从最后一个RDD到最开始的RDD,都没有被cache或checkpoint,那么最佳位置就是Nil,即没有最佳位置)。针对stage的task,创建TaskSet对象,调用TaskScheduler的submitTask方法,提交TaskSet,提交到Excutor上去执行

即:

1、从finalstage倒推,
2、通过宽依赖进行新的stage划分
3、使用递归,优先提交父stage

源码剖析

Stage的划分

  进入submitJob方法,首先会去检查rdd的分区信息,在确保rdd分区信息正确的情况下,给当前job生成一个jobId,从0开始编号,在同一个SparkContext中,jobId会逐渐顺延。然后构造出一个JobWaiter对象返回给上一级调用函数。通过eventProcessLoop提交该任务,最终会调用到DAGScheduler.handleJobSubmitted来处理这次提交的Job。

DAGScheduler.eventProcessLoop.post( JobSubmitted(...) )
DAGScheduler.handleJobSubmitted

  在DAGScheduler内部通过post一个JobSubmitted事件来触发Job的提交。EventProcessLoop类继承自EventLoop类,其中的post方法也是在EventLoop中定义的。在EventLoop中维持了一个LinkedBlockingDeque类型的事件队列,将该Job提交事件存入该队列后,事件线程会从队列中取出事件并进行处理。
  提交的JobSubmitted事件,实际上调用了DAGScheduler的handleJobSubmitted方法。DAGScheduler.handleJobSubmitted通过调用newResultStage函数来创建finalStage。调用newResultStage时,传入了finalRDD、partitions.size等参数。

finalStage = newResultStage(finalRDD, partitions.size, jobId, callSite)

  在DAGScheduler.newResultStage首先调用getParentStagesAndId(rdd, jobId)。这个方法主要是为当前的RDD向前探索,找到宽依赖处划分出parentStage,并为当前RDD所属Stage生成一个stageId。在这个方法中,getParentStages的调用链最终递归调用到了这个方法,所以,最后一个Stage的stageId最大,越往前的stageId就越小,stageId小的Stage先执行。
  跟到getParentStages里。在函数getParentStages中,遍历整个RDD依赖图的finalRDD的List[dependency] ,若遇到ShuffleDependency(即宽依赖),则调用getShuffleMapStage(shufDep, jobId)返回一个ShuffleMapStage类型对象,添加到父stage列表中。而窄依赖的RDD,继续压入栈中,直到遇到ShuffleDependency或无依赖的RDD。
  跟进到函数getShuffleMapStage的实现。getShuffleMapStage为当前宽依赖的Map端生成一个新的ShuffleMapStage类型的Stage。同时也为当前Shuffle的父Shuffle生成一个Stage。通过DAGScheduler.getAncestorShuffleDependencies获取当前Shuffle的父Shuffle,这个方法的实现与getParentStages基本一致,不同的是这里是将宽依赖加入到parents中并返回。
  registerShuffleDependencies拿到各个“依赖路线”最近的所有宽依赖后。对每个宽依赖调用newOrUsedShuffleStage,该函数用来创建新ShuffleMapStage或获得已经存在的ShuffleMapStage。
  函数newOrUsedShuffleStage首先调用newShuffleMapStage来创建新的ShuffleMapStage。newShuffleMapStage调用getParentStagesAndId来获取它的parentStages。那么,整个函数调用流程又会继续走一遍,不同的是起点rdd不是原来的finalRDD而是变成了这里的宽依赖的rdd。

Stage的提交

任务的提交和生成入口在DAGScheduler.handleJobSubmitted方法中。

DAGScheduler.handleJobSubmitted
  生成了finalStage后,就会为该Job生成一个ActiveJob对象了,并准备计算这个finalStage。在DAGScheduler.handleJobSubmitted方法的最后,调用了DAGScheduler.submitStage方法,在提交finalStage的前面,会通过listenerBus的post方法,把Job开始的事件提交到Listener中。

DAGScheduler#submitStage
  Job的提交,是从最后那个Stage开始的。如果当前stage已经被提交过,处于waiting状态或者当前stage已经处于failed状态则不作任何处理,否则继续提交该stage。
  在提交时,需要当前Stage需要满足依赖关系,其前置的Parent Stage都运行完成后才能轮得到当前Stage运行。如果还有Parent Stage未运行完成,则优先提交Parent Stage。通过调用方法DAGScheduler.getMissingParentStages方法获取未执行的Parent Stage。如果当前Stage满足上述两个条件后,调用DAGScheduler.submitMissingTasks方法,提交当前Stage。

DAGScheduler.getMissingParentStage
  这个方法用于获取stage未执行的Parent Stage。在上面方法中,获取到Parent Stage后,递归调用上面那个方法按照StageId小的先提交的原则,这个方法的逻辑和DAGScheduler.getParentStages方法类似。总之就是根据当前Stage,递归调用其中的visit方法,依次对每一个Stage追溯其未运行的Parent Stage。

DAGScheduler.submitMissingTasks
  当Stage的Parent Stage都运行完毕,才能调用这个方法真正的提交当前Stage中包含的Task。

DAG生成

  DAG是有向无环图,原始的RDD通过一系列的转换就形成了DAG,根据RDD之间依赖关系的不同将DAG划分成不同的Stage(调度阶段)。对于窄依赖,partition的转换处理在一个Stage中完成计算。对于宽依赖,由于有Shuffle的存在,只能在parent RDD处理完成后,才能开始接下来的计算,因此宽依赖是划分Stage的依据。一个Spark的Application应用中一个或者多个DAG(也就是一个Job),取决于触发了多少次Action。一个DAG中会有不同的阶段/stage,划分阶段/stage的依据就是宽依赖。一个阶段/stage中可以有多个Task,一个分区对应一个Task。

DAG的边界:
开始:通过SparkContext创建的RDD
触发Action,一旦触发Action就形成了一个完整的DAG

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值