RDD之getNarrowAncestors内部方法分析
最近开始spark的源码攻关,其实看源码一直是我最怕的东西,因为太多、太杂、太深导致不能够很好的把我脉络导致每次最后都放弃。有人跟我说看源码可以阶段性一个方法一个方法的去学习,去看,每天积累一点总会成功,那么今天开始我的第一天spark源码分析。
我这里从spark最基本的RDD中的方法说起,我感觉这样会更容易一些。同时我只对其中感觉比较重要的方法进行一些讲解。今天我主要讲一下getNarrowAncestors方法,也就是所谓的获取窄依赖的父RDD的方法,这个方法是一个内部方法,方法整体如下:
/** * Return the ancestors of the given RDD that are related to it only through a sequence of * narrow dependencies. This traverses the given RDD's dependency tree using DFS, but maintains * no ordering on the RDDs returned. */ private[spark] def getNarrowAncestors: Seq[RDD[_]] = { val ancestors = new mutable.HashSet[RDD[_]] def visit(rdd: RDD[_]) { val narrowDependencies = rdd.dependencies.filter(_.isInstanceOf[NarrowDependency[_]]) val narrowParents = narrowDependencies.map(_.rdd) val narrowParentsNotVisited = narrowParents.filterNot(ancestors.contains) narrowParentsNotVisited.foreach { parent => ancestors.add(parent) visit(parent) } } visit(this) // In case there is a cycle, do not include the root itself ancestors.filterNot(_ == this).toSeq }
方法的返回值为Seq类型,里面包含RDD,这不难理解,一个RDD他是窄依赖,那么他有一个父RDD,但是你不能保证他的父RDD没有依赖,所以返回结果为一个序列,里面装有很多的RDD。然后一开始我们看到一个HashSet,他的里面装有RDD(不过目前是空的),为什么用HashSet?好问题,因为它里面没有重复,我们不需要获取一个含有重复的序列(就像别人问你家里都有谁,你不会说两次爸爸一样)。下面定义了一个方法visit,首先这里很有意思,因为我们在一个方法里面定义了另外一个方法,所以你不得不佩服scala的灵活性。
这个visit方法需要传入一个RDD作为参数。然后我们进入方法第一行,rdd调用他的dependencies方法,以下是dependencies方法:
/** * Get the list of dependencies of this RDD, taking into account whether the * RDD is checkpointed or not. */ final def dependencies: Seq[Dependency[_]] = { checkpointRDD.map(r => List(new OneToOneDependency(r))).getOrElse { if (dependencies_ == null) { dependencies_ = getDependencies } dependencies_ } }
这个方法同样返回一个Seq里面是Dependecy的类型,Dependency类型是一个抽象类,很简单,整体如下:
abstract class Dependency[T] extends Serializable { def rdd: RDD[T] }
只有一个RDD,那么回到上面dependencies方法中,第一行,我们查看checkpointRDD。可能有很多人不知道这是什么,那么请查看如下代码:
/** An Option holding our checkpoint RDD, if we are checkpointed */ private def checkpointRDD: Option[CheckpointRDD[T]] = checkpointData.flatMap(_.checkpointRDD)
可以看到,checkpointRDD是一个Option类型里面包含的RDD是我们已经checkpointed的。我的理解是,这句可以理解为一个过滤过程。回到上面的dependencies方法中,我们看到checkpointRDD对其内的每个元素(也就是CheckpointRDD类型的RDD)执行方法OneToOneDependency(),代码如下:
/** * :: DeveloperApi :: * Represents a one-to-one dependency between partitions of the parent and child RDDs. */ @DeveloperApi class OneToOneDependency[T](rdd: RDD[T]) extends NarrowDependency[T](rdd) { override def getParents(partitionId: Int): List[Int] = List(partitionId) }
这里为不引起混乱,不在进入更深的方法,这个方法的含义是获取所有的子RDD与父RDD的对应关系为1对1的父RDD的分区号。回到denpendencies方法中,我们看到下面有个方法getDependencies顾名思义,获取所有的依赖(此处方法不在深入),此时dependencies方法返回结果,返回一开始的visit方法中,为了方便查看,我再打印出来一遍:
def visit(rdd: RDD[_]) { val narrowDependencies = rdd.dependencies.filter(_.isInstanceOf[NarrowDependency[_]]) val narrowParents = narrowDependencies.map(_.rdd) val narrowParentsNotVisited = narrowParents.filterNot(ancestors.contains) narrowParentsNotVisited.foreach { parent => ancestors.add(parent) visit(parent) } }
获取到值进行filter过滤,过滤的条件是查看里面的RDD是否为NarrowDependency类型。然后我们将过滤后的值保存变量名字为:narrowDependencies,然后将依赖中的rdd取出保存变量名字为narrowParents,也就是所谓的父RDD。然后下面几段代码的目的是把父RDD放入到我们一开始创建的HashSet中,重复的去掉,没有的加入。
再回到getNarrowAncestors方法中,我们看到下面它调用了visit方法,把本身作为参数传入。最后那一句是为了过滤出父类中又包含自身RDD这种循环而存在的,把过滤后的转化为Seq作为结果传出。