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