Cogroup
cogroup的函数实现:
这个实现根据两个要进行合并的两个RDD操作,生成一个CoGroupedRDD的实例,这个RDD的返回结果是把相同的key中两个RDD分别进行合并操作,最后返回的RDD的value是一个Pair的实例,这个实例包含两个Iterable的值,第一个值表示的是RDD1中相同KEY的值,第二个值表示的是RDD2中相同key的值.
由于做cogroup的操作,需要通过partitioner进行重新分区的操作,因此,执行这个流程时,需要执行一次shuffle的操作(如果要进行合并的两个RDD的都已经是shuffle后的rdd,同时他们对应的partitioner相同时,就不需要执行shuffle,)
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) {
throw new SparkException("Default partitioner cannot partition array keys.")
}
生成CoGroupedRDD的实例,并根据这个实例执行mapValues的操作.这个的结果是一个Pair的实例,
Pair._1是左RDD对应key的结果集,Pair._2是右RDD对应key的结果集.
val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)
cg.mapValues { case Array(vs, w1s) =>
(vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])
}
}
这里说明下关于GoGroupedRDD的一些关键代码:
重写的getDependencies函数:
rdds.map { rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
logDebug("Adding one-to-one dependency with " + rdd)
new OneToOneDependency(rdd)
} else {
针对这个CoGroupedRDD的实例上层依赖的两个RDD中,如果上层的RDD不是一个SHUFFLE的RDD,或者说上层的RDD的SHUFFLE算子与当前的RDD的算子不相同时,
这个RDD在执行时,还需要进行SHUFFLE的操作.
在生成这个依赖时使用到的CoGroupCombiner类型=>
private type CoGroup = CompactBuffer[Any]
private type CoGroupValue = (Any, Int) // Int is dependency number
private type CoGroupCombiner = Array[CoGroup]
logDebug("Adding shuffle dependency with " + rdd)
new ShuffleDependency[K, Any, CoGroupCombiner](
rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
}
}
针对CoGroupedRDD的compute函数的关键部分代码:
这个函数中,首先根据CoGroupedRDD的上层的依赖的RDD,生成一个rddIterators的数组.每个RDD的结果的Iterator都存储到数组对应的index中.
val rddIterators = new ArrayBuffer[(Iterator[Product2[K, Any]], Int)]
这里根据RDD对上层的依赖的RDD进行迭代,得到上层RDD中对应的部分的数据集的Iterator,并存储到对应的数组位置中.
for ((dep, depNum) <- dependencies.zipWithIndex) dep match {
case oneToOneDependency: OneToOneDependency[Product2[K, Any]] @unchecked =>
上层依赖的RDD是一个OneToOneDependency的RDD时,表示这个RDD的SHUFFLE算子与上层的RDD的SHUFFLE的算子相同,不需要进行SHUFFLE的操作,直接得到上层RDD的iterator的结果集,存储到对应的数组index中.
val dependencyPartition = split.narrowDeps(depNum).get.split
// Read them from the parent
val it = oneToOneDependency.rdd.iterator(dependencyPartition, context)
rddIterators += ((it, depNum))
case shuffleDependency: ShuffleDependency[_, _, _] =>
这种情况下,表示在执行这个RDD前,后一个SHUFFLE的执行,对上层依赖的RDD进行了重新分区的操作,通过shuffleManager的reader,得到上层RDD中重新对应此分区数据的数据集.存储到对应的数组index中.
// Read map outputs of shuffle
val it = SparkEnv.get.shuffleManager
.getReader(shuffleDependency.shuffleHandle, split.index, split.index + 1, context)
.read()
rddIterators += ((it, depNum))
}
接下来的代码中首先生成一个ExternalAppendOnlyMap[K, CoGroupValue, CoGroupCombiner]实例,在这个生成的实例中,K表示这个RDD的KEY的类型,
CoGroupValue类型为CoGroupValue = (Any, Int).这里的Any其实是上层RDD的VLAUE的类型.
CoGroupCombiner类型为Array[CoGroup].这个数组的长度为2.对应上层的两个RDD.
CoGroup类型为CompactBuffer[Any].这里存储的Any是对应具体的上层RDD的值.
也就是key相同的value的集合体.
val map = createExternalMap(numRdds)
for ((it, depNum) <- rddIterators) {
迭代每个RDD的返回的iterator的结果,每次迭代时,把上层rdd的value包装成CoGroupValue的实例.
把每个RDD中的结果通过ExternalAppendOnlyMap把相同的key进行合并操作.这个操作与shuffle中的groupByKey与reduceByKey的操作中使用到的三个函数的流程完全相同,
map.insertAll(it.map(pair => (pair._1, new CoGroupValue(pair._2, depNum))))
}
接下来看看在CoGroupedRDD中数据compute的计算时,ExternalAppendOnlyMap合并结果集时的三个操作函数的定义:
createCombiner函数,这个函数在key第一次被发现时,用于生成CoGroupCombiner的实例,并把这个value添加到这个实例数组中对应的位置上(根据上层RDD对应的位置)
val createCombiner: (CoGroupValue => CoGroupCombiner) = value => {
val newCombiner = Array.fill(numRdds)(new CoGroup)
newCombiner(value._2) += value._1
newCombiner
}
mergeValue函数,这个函数把key相同的value合并对对应此上层RDD的位置的Buffer的集合中.这个主要是针对一个相同的partition地结果的合并.
val mergeValue: (CoGroupCombiner, CoGroupValue) => CoGroupCombiner =
(combiner, value) => {
combiner(value._2) += value._1
combiner
}
mergeCombiners函数,把两个buffer进行合并,最终生成一个buffer,主要用于多partition合并.
val mergeCombiners: (CoGroupCombiner, CoGroupCombiner) => CoGroupCombiner =
(combiner1, combiner2) => {
var depNum = 0
while (depNum < numRdds) {
combiner1(depNum) ++= combiner2(depNum)
depNum += 1
}
combiner1
}