前言
本篇文章从源码角度分析下join、leftouterjoin、rightouterjoin、fullouterjoin、intersection五个算子的基本原理,他们底层都是依赖cogroup算子,关于cogroup算子的原理请参考。下面分别分析以上五个算子的源码
join
def join[W](other: RDD[(K, W)], partitioner: Partitioner): RDD[(K, (V, W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues( pair =>
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, w)
)
}
join算子实现的功能就是hive sql中的inner join
首先两个RDD做一个cogroup运算,得到一个新的RDD(CoGroupedRDD),其元素类型为(key, (iterable, iterable))(具体解释请参考)
对新生成的CoGroupedRDD做flatMapValues(原理请参考transformation算子基本原理二)运算。CoGroupedRDD的value类型为(iterable, iterable),遍历两个iterable(for (v <- pair._1.iterator; w <- pair._2.iterator)),即做笛卡尔积,两个迭代器都不为空则将每一对元素装入一个集合(yield (v, w)),最后对返回的集合做flat操作。注意join算子的返回类型是RDD[(K, (V, W))],注意value的类型(V, W),V和W是不同的类型,表示原始两个RDD中元素(k,v)的v类型是可以不同的
leftoutjoin
def leftOuterJoin[W](
other: RDD[(K, W)],
partitioner: Partitioner): RDD[(K, (V, Option[W]))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues { pair =>
// 先判断右迭代器是否为空
if (pair._2.isEmpty) {
pair._1.iterator.map(v => (v, None))
} else {
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (v, Some(w))
}
}
}
leftoutjoin算子原理和join算子原理一样,只是比join多了一个判断:
if (pair._2.isEmpty) {pair._1.iterator.map(v => (v, None)) }。大家知道对于sql中的左连接,不管右表是否有记录,左表的记录都保留,右表字段都为null。所以对于leftoutjoin,先要判断右边的迭代器是否为空,如果为空,直接返回左边迭代器的元素
rightoutjoin
def rightOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Option[V], W))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues { pair =>
// 先判断左迭代器是否为空
if (pair._1.isEmpty) {
pair._2.iterator.map(w => (None, w))
} else {
for (v <- pair._1.iterator; w <- pair._2.iterator) yield (Some(v), w)
}
}
}
rightoutjoin算子原理和join算子原理一样,只是比join多了一个判断:
if (pair._2.isEmpty) {pair._1.iterator.map(v => (v, None)) }。大家知道对于sql中的右连接,不管左表是否有记录,右表的记录都保留,左表字段都为null。所以对于rightoutjoin,先要判断左边的迭代器是否为空,如果为空,直接返回右边迭代器的元素
fullouterjoin
def fullOuterJoin[W](other: RDD[(K, W)], partitioner: Partitioner)
: RDD[(K, (Option[V], Option[W]))] = self.withScope {
this.cogroup(other, partitioner).flatMapValues {
case (vs, Seq()) => vs.iterator.map(v => (Some(v), None))
case (Seq(), ws) => ws.iterator.map(w => (None, Some(w)))
case (vs, ws) => for (v <- vs.iterator; w <- ws.iterator) yield (Some(v), Some(w))
}
}
sql中,full outer join 左右表的记录都需要保留,fullouterjoin的算子逻辑也是此原理:
case (vs, Seq()) => vs.iterator.map(v => (Some(v), None)):如果右迭代器为空,直接返回左迭代器元素;
case (Seq(), ws) => ws.iterator.map(w => (None, Some(w))):如果左迭代器为空,直接返回右迭代器元素;
case (vs, ws) => for (v <- vs.iterator; w <- ws.iterator) yield (Some(v), Some(w)):如果都不为空,返回左右迭代器元素的笛卡尔积
intersection
def intersection(other: RDD[T]): RDD[T] = withScope {
this.map(v => (v, null)).cogroup(other.map(v => (v, null)))
.filter { case (_, (leftGroup, rightGroup)) => leftGroup.nonEmpty && rightGroup.nonEmpty }
.keys
}
和上述四个算子属于PairRDDFunctions类不同的是,intersection在RDD类中。其作用就是两个RDD做交集。
- 首先将两个RDD做一个转化,v => (v, null), 转化为(k,v)类型的RDD
- 然后对上述两个新RDD做一个cogroup计算,得到一个CoGroupedRDD
- 然后对CoGroupedRDD中的元素进行过滤,左右迭代器同时不为空(注意,不是迭代器中的元素不为空,是迭代器中是否有元素。迭代器中的元素都是null元素)为真,和join算子的原理差不多
- 最后对返回的RDD做一个keys的操作,因为返回的元素类型是(_, (leftGroup, rightGroup)),但是我们只需要key即可
大家是不是感觉intersection和join原理差不多,但是他们有两个不同点:
- intersection可以作用在任何类型的RDD上,而join是作用在(k,v)类型的RDD上
- 参与intersection计算的两个RDD的元素类型必须一样,join算子的v类型可以不一致