场景
我正在尝试编写一个Spark程序,该程序可以有效地执行两个RDD之间的左外部联接。一个警告是这些RDD可以具有重复的密钥,这显然导致整个程序效率低下。
我要实现的目标很简单:
rdd1
和rdd2
(两者具有相同的结构:(k, v)
)rdd1
和rdd2
生成另一个结构为rdd3
的RDD (k1, v1, List(v2..))
k1
和v1
来自rdd1
(相同的值,这将导致rdd1
和rdd3
具有相同的长度)List(v2..)
是一个列表,其值来自rdd2
rdd2
的{{1}}添加到v
的元组的列表中,其rdd3
(k
的键)应与rdd2
中的k
我的尝试
我的方法是使用左外部联接。所以,我想到了这样的东西:
rdd1
这实际上产生了我要达到的结果。但是,当我使用大量数据时,该程序将变得非常慢。
示例
以防万一,我的例子如下:
给出两个具有以下数据的RDD:
rdd1.leftOuterJoin(rdd2).map{case(k, (v1, v2)) => ((k, v1), Array(v2))}
.reduceByKey(_ ++ _)
:
rdd1
key | value
-----------
1 | a
1 | b
1 | c
2 | a
2 | b
3 | c
:
rdd2
结果key | value
-----------
1 | v
1 | w
1 | x
1 | y
1 | z
2 | v
2 | w
2 | x
3 | y
4 | z
应该是
rdd3
答案 0 :(得分:2)
首先不要使用:
map { ... => (..., Array(...)) }.reduceByKey(_ ++ _)
这几乎就是效率低下。要使用RDD将此类值分组,您实际上应该使用groupByKey
。
此外,之后仅groupByKey
浪费很大。您在右侧做了两次相同的工作(按键分组)。直接使用cogroup
(RDD联接的工作方式)和flatMap
val rdd1 = sc.parallelize(Seq(
(1, "a"), (1, "b"), (1, "c"), (2, "a"), (2, "b"),(3, "c")
))
val rdd2 = sc.parallelize(Seq(
(1, "v"), (1, "w"), (1, "x"), (1, "y"), (1, "z"), (2, "v"),
(2, "w"), (2, "x"), (3, "y"),(4, "z")
))
val rdd = rdd1
.cogroup(rdd2)
.flatMapValues { case (left, right) => left.map((_, right)) }
.map { case (k1, (k2, vs)) => ((k1, k2), vs) }
您还可以使用DataSet
API,在这种情况下该API往往更有效
import org.apache.spark.sql.functions.collect_list
val df1 = rdd1.toDF("k", "v")
val df2 = rdd2.toDF("k", "v")
df2.groupBy("k")
.agg(collect_list("v").as("list"))
.join(rdd1.toDF("k", "v"), Seq("k"), "rightouter")
.show
结果:
+---+---------------+---+
| k| list| v|
+---+---------------+---+
| 1|[v, w, x, y, z]| a|
| 1|[v, w, x, y, z]| b|
| 1|[v, w, x, y, z]| c|
| 3| [y]| c|
| 2| [v, w, x]| a|
| 2| [v, w, x]| b|
+---+---------------+---+
如果键组的相交很小,则可以尝试通过首先应用过滤器来优化过程
val should_keep = {
val f = df1.stat.bloomFilter("k", df1.count, 0.005)
udf((x: Any) => f.mightContain(x))
}
df2.where(should_keep($"k")).groupBy("k")
.agg(collect_list("v").as("list"))
.join(rdd1.toDF("k", "v"), Seq("k"), "rightouter")
.show
+---+---------------+---+
| k| list| v|
+---+---------------+---+
| 1|[v, w, x, y, z]| a|
| 1|[v, w, x, y, z]| b|
| 1|[v, w, x, y, z]| c|
| 3| [y]| c|
| 2| [v, w, x]| a|
| 2| [v, w, x]| b|
+---+---------------+---+
使用Dataset
API时,请确保调整spark.sql.shuffle.partitions
以反映您处理的数据量。
注意:
如果rdd2
中重复项的数量很大,那么这些都不会帮助您。在这种情况下,总的问题表述是无法辩护的,您应该考虑下游过程的要求,重新制定表述。