1.1spark源码阅读笔记 RDD MapParitionsRDD

刚刚开始学习scala,对于spark又有深入了解的需求,索性开始阅读spark的源码,之前照着spark-streaming-kafka的代码抄了一份spark连接cannal的代码,现在希望开始从spark最基础的spark-core模块开始深入了解一下

1 RDD

RDD是spark中最基本的逻辑数据单元,源码在RDD类记录了所有RDD共同的转换操作和行动操作,比如常用的MAP,Mappartitions,collect等等,首先看一下RDD的构造函数

abstract class RDD[T: ClassTag](
    @transient private var _sc: SparkContext,
    @transient private var deps: Seq[Dependency[_]]
  ) extends Serializable with Logging

可以看到RDD是一个抽象类,对应不同的情况下会实例化出不同的RDD,其中参数 sc 表示运行的sparkcontext,deps 为这个RDD与父RDD所依赖的依赖关系,根据这个dependency类可以找到这个RDD对应的父RDD,可以理解为spark dag执行顺序的连接脉络。

其中Dependency类有两个子类,分别对应宽依赖和窄依赖,具体宽依赖和窄依赖的原理就不细说了,可以暂时理解为依赖有可能只依赖一个父RDD,也有可能依赖多个父RDD(这不代表宽依赖多个RDD,窄依赖只依赖一个RDD哦),所以deps为dependency的一个列表

接下来是RDD中几个重要抽象方法

/** * 记录了rdd接下来要进行的操作 */
@DeveloperApi
def compute(split: Partition, context: TaskContext): Iterator[T]

/**
 * 如何分partition
 */
protected def getPartitions: Array[Partition]


/**
 * partition具体分发到那个work的策略实现
 */
protected def getPreferredLocations(split: Partition): Seq[String] = Nil

在自己实现rdd时最基本要实现的几个方法就是上述三个,以实现类MapPartitionsRDD为例,看看具体如何实现上述方法

private[spark] class MapPartitionsRDD[U: ClassTag, T: ClassTag](
    var prev: RDD[T],
    f: (TaskContext, Int, Iterator[T]) => Iterator[U],  // (TaskContext, partition index, iterator)
    preservesPartitioning: Boolean = false)
  extends RDD[U](prev) {

  override val partitioner = if (preservesPartitioning) firstParent[T].partitioner else None

  override def getPartitions: Array[Partition] = firstParent[T].partitions

  override def compute(split: Partition, context: TaskContext): Iterator[U] =
    f(context, split.index, firstParent[T].iterator(split, context))

构造函数中prev为父RDD,f为接下来的操作,如map,mappartition等。在构造函数中,调用了父类RDD的单参数构造方法

def this(@transient oneParent: RDD[_]) =
  this(oneParent.context, List(new OneToOneDependency(oneParent)))

其中OneToOneDependency,为窄依赖NarrowDependency的一个子类,这个构造函数表明,构成一个MapPartitionsRDD的操作一定是一个窄依赖操作,而且只依赖于一个父RDD,不会发生shuffle,所以context就也可以直接利用父RDD的context,因为同在一个stage中。

接下来

override def getPartitions: Array[Partition] = firstParent[T].partitions

由于是一对一的窄依赖,所以与父RDD的partition也不会发生变化,直接使用了firstParent。这里firstParent是之前说到RDD构造中可能会有多个依赖,所以是个依赖的list,firstParent是去依赖中的第一个依赖中的RDD(感觉这里我理解也不甚到位,可以暂时这么理解吧)

再然后

override def compute(split: Partition, context: TaskContext): Iterator[U] = f(context, split.index, firstParent[T].iterator(split, context))

conpute函数如前面所说,是记录了这个mapRDD接下来要进行的操作,这样说可能有些迷茫,我们以mapPartitions方法举个例子,

val x=sc.parallelize(1 to 9)
val partitionRDD=x.mapPartitions(it=>it.map(_+3))
partitionRDD.collect()

x为ParallelCollectionRDD,也是RDD的一个实现类,没有依赖的父RDD,暂时先按一个普通RDD处理。

然后,再x进行mapPartitions后,生成了一个mapPartitionsRDD,此时mapPartitionsRDD的依赖为x,compute中记录了it=>it.map(_+3)这个方法,就是将迭代器中每个元素加三这个方法。

最后partitionRDD执行RDD中公共的方法

def collect(): Array[T] = withScope {
  val results = sc.runJob(this, (iter: Iterator[T]) => iter.toArray)
  Array.concat(results: _*)
}

可以看到collect会提交job,并且将每个partition中compute的结果,变成array,最后拼接到一起,至此一个简单的job就完成了,具体runjob中式怎样执行的,需要理解spark中另外一个重要的类dagscheduler。

之后会再去理解一下宽依赖和dagscheduler,希望自己能够坚持下去吧,第一次写,乱糟糟的,各位看官见谅

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值