这个transform返回的是进行操作的两个RDD中,key-value都相同的所有的数据集的新的RDD.说白了就是把两个RDD中数据完全相同的数据进行保留,不相同的数据直接丢弃掉.这个操作会执行shuffle操作.
实现代码:
def intersection(other: RDD[T]): RDD[T] = withScope {
这里首先执行对第一个rdd的map操作,执行完成map操作后,新生成的map rdd的key就是原来rdd的kv,value就一个null,
其次执行对第二个rdd的map操作,执行完成map操作后,新生成的map rdd的key就是原来rdd的kv,value就一个null,
第三步:把两个执行完成map操作后的新的两个rdd,这两个rdd的key都变成了原来的kv,value都是null,把这两个新的rdd执行cogroup的操作,这个操作把两个rdd中相同的key的数据进行合并,value就一个数组,数组的长度是2(两个rdd),数组中第0个下标有值,表示这个key在第0个rdd中存在,第1个下标有值,表示这个key在第1个下标有值,如果两个都有值,表示这个key(原来老的rdd的key-value)在两个rdd中都存在.
this.map(v => (v, null)).cogroup(other.map(v => (v, null)))
第4步,执行filter操作,这个操作过滤掉key对应的value的数组中,只包含一个值的所有的数据,也就是说这个filter完成后,返回的rdd中的数据是合并操作的两个rdd中key与value都相同的所有的数据的新的RDD的数据集.
.filter { case (_, (leftGroup, rightGroup)) =>
leftGroup.nonEmpty && rightGroup.nonEmpty }
最后一个操作,这里返回合并后,两个RDD都包含的key-value的key的部分的值,这个值,其实就是原始的rdd的key-value.
.keys
}
实现代码的分析:
首先,根据当前的rdd做map操作,这个作用是把原来的kv当作k,value为null.
然后,把传入的要进行处理的rdd做同样的操作.
接下来,开始做cogroup操作,通过这两个RDD,
def cogroup[W](other: RDD[(K, W)]): RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
cogroup(other, defaultPartitioner(self, other))
}
来看看defaultPartitioner的实现:
这里根据两个RDD中,根据RDD中的PARTITION的个数,倒排序,并进行迭代,如果传入的RDD中某一个RDD包含有partitioner的算子时,直接使用这个RDD的paritioner算子.
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val bySize = (Seq(rdd) ++ others).sortBy(_.partitions.size).reverse
for (r <- bySize if r.partitioner.isDefined
&& r.partitioner.get.numPartitions > 0) {
return r.partitioner.get
}
流程执行到这里,表示传入的两个RDD,都没有partitioner,这个时候,如果下面代码中的配置项存在,直接使用这个配置的默认的并行度来生成HashPartitioner,否则根据RDD的partition个数最多的partitions进行生成HashPartitioner实例.
if (rdd.context.conf.contains("spark.default.parallelism")) {
new HashPartitioner(rdd.context.defaultParallelism)
} else {
new HashPartitioner(bySize.head.partitions.size)
}
}
根据生成好的Partitioner实例,再次调用cogroup的重载.
def cogroup[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Iterable[V], Iterable[W]))] = self.withScope {
这里进行下检查,如果是Hash分区的算子时,key不能是array,array不支持hashcode.
if (partitioner.isInstanceOf[HashPartitioner] && keyClass.isArray) {
throw new SparkException("Default partitioner cannot partition array keys.")
}
这里生成一个CoGroupedRDD的实例.
val cg = new CoGroupedRDD[K](Seq(self, other), partitioner)
cg.mapValues { case Array(vs, w1s) =>
(vs.asInstanceOf[Iterable[V]], w1s.asInstanceOf[Iterable[W]])
}
}
下面来看看CoGroupedRDD的定义的关键部分:
这个部分包含实例的生成,上层RDD依赖,PARTITION,与compute这几个部分:
实例生成:在这个实例生成时,默认没有指定上层RDD的依赖.
class CoGroupedRDD[K: ClassTag](
@transient var rdds: Seq[RDD[_ <: Product2[K, _]]],
part: Partitioner)
extends RDD[(K, Array[Iterable[_]])](rdds.head.context, Nil)
由于在实例生成时没有指定RDD的依赖,因此,这个RDD的依赖通常是一个特殊的依赖
接下来看看getDependencies函数.
在这个函数中,如果上层的RDD中包含有partitioner时,那么针对这个rdd的依赖是一个OneToOneDependency的依赖,如果上层的RDD中包含的partitioner与当前新生成的RDD的partitioner是同一个实例时,那么针对这个rdd的依赖是一个OneToOneDependency的依赖,
如果上层的两个RDD都不包含partitioner时,两个RDD的上层依赖都是ShuffleDependency.
如果生成的上层RDD的依赖是ShuffleDependency时,针对这个RDD需要单独生成stage执行.
override def getDependencies: Seq[Dependency[_]] = {
rdds.map { rdd: RDD[_] =>
if (rdd.partitioner == Some(part)) {
logDebug("Adding one-to-one dependency with " + rdd)
new OneToOneDependency(rdd)
} else {
logDebug("Adding shuffle dependency with " + rdd)
new ShuffleDependency[K, Any, CoGroupCombiner](
rdd.asInstanceOf[RDD[_ <: Product2[K, _]]], part, serializer)
}
}
}
下面再看看CoGroupedRDD中getPartitions函数:
针对一个cogroup的rdd,这个rdd的partition的个数由Partitioner对应的partition的个数得到.
override def getPartitions: Array[Partition] = {
val array = new Array[Partition](part.numPartitions)
for (i <- 0 until array.length) {
迭代生成当前的RDD对应的每一个的partition.
在生成这个Partition时,实例的第一个参数是对应的partition的下标,
第二个参数是一个数组,这个数组是当前的partition对应的上层的RDD的依赖的RDD,与RDD对应的Partition.如果上层依赖是一个ShuffleDependency时,数组对应此RDD的下标位置是一个None.
也就是说Partition第二个参数对应的上层依赖RDD的下标位置如果不是一个None时,那么针对这个上层的RDD就是一个OneToOneDependency的依赖.
// Each CoGroupPartition will have a dependency per contributing RDD
array(i) = new CoGroupPartition(i, rdds.zipWithIndex.map { case (rdd, j) =>
// Assume each RDD contributed a single dependency, and get it
dependencies(j) match {
case s: ShuffleDependency[_, _, _] =>
None
case _ =>
Some(new NarrowCoGroupSplitDep(rdd, i, rdd.partitions(i)))
}
}.toArray)
}
array
}
最后,看看针对CoGroupedRDD的compute的实现逻辑:
override def compute(s: Partition, context: TaskContext)
: Iterator[(K, Array[Iterable[_]])] = {
得到要计算的partition,并得到这个RDD依赖的上层RDD的个数.
val split = s.asInstanceOf[CoGroupPartition]
val numRdds = dependencies.length
这个rddIterators中第一个参数是对应上层依赖RDD中此Partition的Iterator,第二个参数是对应上层依赖的rdd的index.
// A list of (rdd iterator, dependency number) pairs
val rddIterators = new ArrayBuffer[(Iterator[Product2[K, Any]], Int)]
这里得到每个依赖的Dependency与对应数组的index.
for ((dep, depNum) <- dependencies.zipWithIndex) dep match {
如果对应此依赖的RDD与当前的RDD的partitioner是相同的partitioner实例时,表示上层的shuffle与这个RDD的shuffle的算子相同,这个时候,直接得到对应的依赖的对应的Partition,并通过上层依赖的RDD的iterator来得到上层的依赖的RDD对应此PARTITION的Iterator的实例.
case oneToOneDependency: OneToOneDependency[Product2[K, Any]] @unchecked =>
val dependencyPartition = split.narrowDeps(depNum).get.split
// Read them from the parent
val it = oneToOneDependency.rdd.iterator(dependencyPartition, context)
rddIterators += ((it, depNum))
如果上层依赖是一个shuffle的依赖,这个时候表示当前的RDD的partitioner与上层的partitioner是不相同的shuffle算子,这个RDD的执行时,上层依赖的rdd stage已经执行完成,通过shuffleManager与当前shuffle过来的partition的index,得到这个shuffle结果的reader并生成Iterator的实例.先要执行shuffle的结果的读操作.
case shuffleDependency: ShuffleDependency[_, _, _] =>
// Read map outputs of shuffle
val it = SparkEnv.get.shuffleManager
.getReader(shuffleDependency.shuffleHandle, split.index, split.index + 1,
context)
.read()
rddIterators += ((it, depNum))
}
这个步骤执行合并操作.
这个合并操作需要注意的是,针对相同的key进行合并,value是一个Array,这个array的长度就是这个rdd的上层依赖rdd的个数,也就是两个.如果数组中某一个下标没有存储值,表示这个key在另一个rdd中不存在,这里的key需要注意的是,是原始的RDD的kv代表着这个RDD的上层依赖的KEY.
val map = createExternalMap(numRdds)
for ((it, depNum) <- rddIterators) {
map.insertAll(it.map(pair => (pair._1, new CoGroupValue(pair._2, depNum))))
}
context.taskMetrics().incMemoryBytesSpilled(map.memoryBytesSpilled)
context.taskMetrics().incDiskBytesSpilled(map.diskBytesSpilled)
context.internalMetricsToAccumulators(
InternalAccumulator.PEAK_EXECUTION_MEMORY).add(map.peakMemoryUsedBytes)
new InterruptibleIterator(context,
map.iterator.asInstanceOf[Iterator[(K, Array[Iterable[_]])]])
}