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_
}
}
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中