Spark:针对重复键的RDD左外部联接优化

时间:2018-11-15 07:55:37

标签: apache-spark join rdd

场景

我正在尝试编写一个Spark程序,该程序可以有效地执行两个RDD之间的左外部联接。一个警告是这些RDD可以具有重复的密钥,这显然导致整个程序效率低下。

我要实现的目标很简单:

  • 给出两个RDD:rdd1rdd2(两者具有相同的结构:(k, v)
  • 使用rdd1rdd2生成另一个结构为rdd3的RDD (k1, v1, List(v2..))
  • k1v1来自rdd1(相同的值,这将导致rdd1rdd3具有相同的长度)
  • List(v2..)是一个列表,其值来自rdd2
  • 的值
  • 要将rdd2的{​​{1}}添加到v的元组的列表中,其rdd3k的键)应与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

1 个答案:

答案 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中重复项的数量很大,那么这些都不会帮助您。在这种情况下,总的问题表述是无法辩护的,您应该考虑下游过程的要求,重新制定表述。