ShuffledRDD 源码

ShuffledRDD源码:

/**
 * :: DeveloperApi ::
 * The resulting RDD from a shuffle (e.g. repartitioning of data).
 * @param prev the parent RDD.
 * @param part the partitioner used to partition the RDD
 * @tparam K the key class.
 * @tparam V the value class.
 * @tparam C the combiner class.
 */
@DeveloperApi
class ShuffledRDD[K: ClassTag, V: ClassTag, C: ClassTag](
    @transient var prev: RDD[_ <: Product2[K, V]],
    part: Partitioner)
  extends RDD[(K, C)](prev.context, Nil) {

K 传入的key的类
V 传入的value的类
C 组合后的类

注意,这里prev加了一个@transient注解,@transient的意思就是序列化时忽略这个变量。这意味着shuffledRDD序列化时,无法将其前面的RDD也序列化,后面的RDD也就无法获取shuffledRDD之前的RDD的引用了,所以shuffledRDD需要从前一个RDD的输出中拉取数据,而不是通过迭代器从源头开始计算。

再看构造shuffledRDD的过程中传入的参数,prev参数是父RDD,deps参数是Nil,也就是说它的依赖是空,不依赖前一个RDD了

  • shuffledRDD有一个setAggregator()方法为rdd的shuffle设置聚合器。
def setAggregator(aggregator: Aggregator[K, V, C]): ShuffledRDD[K, V, C] = {
    this.aggregator = Option(aggregator)
    this
  }
  
// 此方法又涉及到了Aggregator
@DeveloperApi
case class Aggregator[K, V, C] (
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C) {

Aggregator()要经历三个阶段
第一个阶段是第一条记录的处理
第二个阶段是第二条及之后的记录的处理
第三个阶段是合并之前溢写出来多个小文件的处理

  • shuffledRDD有一个getDependencies()方法来获取依赖。
override def getDependencies: Seq[Dependency[_]] = {
    val serializer = userSpecifiedSerializer.getOrElse {
      val serializerManager = SparkEnv.get.serializerManager
      if (mapSideCombine) { //如果开启了map端的预聚合
        serializerManager.getSerializer(implicitly[ClassTag[K]], implicitly[ClassTag[C]])
      } else {
        serializerManager.getSerializer(implicitly[ClassTag[K]], implicitly[ClassTag[V]])
      }
    }
    List(new ShuffleDependency(prev, part, serializer, keyOrdering, aggregator, mapSideCombine))
  }

这个方法最终会返回一个List,List里面new 了一个ShuffleDependency,第一个参数是前一个RDD,第二个参数是分区器,第三个是序列化器,第四个是key是否排序,第五个是聚合器,聚合器存的就是Aggregator类中的三个参数,第六个参数是map端是否聚合。这些参数共同构成了一个ShuffleDependency。

  • shuffledRDD也有一个compute()方法
 override def compute(split: Partition, context: TaskContext): Iterator[(K, C)] = {
    val dep = dependencies.head.asInstanceOf[ShuffleDependency[K, V, C]]
    val metrics = context.taskMetrics().createTempShuffleReadMetrics()
    SparkEnv.get.shuffleManager.getReader(
      dep.shuffleHandle, split.index, split.index + 1, context, metrics)
      .read()
      .asInstanceOf[Iterator[(K, C)]]
  }

它会拿到上一个RDD的依赖,然后通过sparkEnv来获取ShuffleManager,最终返回一个Reader,这个reader调用read()方法返回一个迭代器Iterator。
这里我们发现,它没有调用父类的迭代器,因为前面是一个独立的计算过程,它会将自己的结果输出到一个文件中,shuffledRDD只是从这个文件中拉取上一个计算过程中输出的结果,而不用去重新跑一遍。

  • compute()会被RDD的iterator()方法调用
//此 RDD 的内部方法;如果适用,将从缓存中读取,或以其他方式计算它。
//这应该“不”由用户直接调用,但可供 RDD 的自定义子类的实现者使用。

 final def iterator(split: Partition, context: TaskContext): Iterator[T] = {
    if (storageLevel != StorageLevel.NONE) {
      getOrCompute(split, context)
    } else {
      computeOrReadCheckpoint(split, context)
    }
  }
  • shuffledRDD也有一个setKeyOrdering()方法
// 为rdd shuffle之后的key设置排序规则
def setKeyOrdering(keyOrdering: Ordering[K]): ShuffledRDD[K, V, C] = {
    this.keyOrdering = Option(keyOrdering)
    this
  }
  • shuffledRDD也有一个setMapSideCombine()方法
// 为rdd的shuffle 设置map端的预聚合标志
def setMapSideCombine(mapSideCombine: Boolean): ShuffledRDD[K, V, C] = {
    this.mapSideCombine = mapSideCombine
    this
  }

reduceByKey、aggregateByKey、combineByKey、foldByKey、groupBy、groupByKey底层调用的都是combineByKeyWithClassTag,并在方法中new的ShuffleRDD

reduceByKey 可以先局部聚合,再全局聚合

groupBy、groupByKey 在new的ShuffleRDD方法中设置了mapSideCombine=false

combineByKeyWithClassTag -> ShuffleRDD -> Aggregator

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值