Scala 火花:什么';将2元组键RDD与单键RDD连接起来的最佳策略是什么?

Scala 火花:什么';将2元组键RDD与单键RDD连接起来的最佳策略是什么?,scala,apache-spark,Scala,Apache Spark,我有两个RDD想要加入,它们看起来像这样: val rdd1:RDD[(T,U)] val rdd2:RDD[((T,W), V)] 碰巧的情况是,rdd1的键值是唯一的,rdd2的元组键值也是唯一的。我希望将这两个数据集连接起来,以便获得以下rdd: val rdd_joined:RDD[((T,W), (U,V))] 实现这一目标最有效的方法是什么?以下是我想到的一些想法 备选案文1: val m = rdd1.collectAsMap val rdd_joined = rdd2.map

我有两个RDD想要加入,它们看起来像这样:

val rdd1:RDD[(T,U)]
val rdd2:RDD[((T,W), V)]
碰巧的情况是,
rdd1
的键值是唯一的,
rdd2
的元组键值也是唯一的。我希望将这两个数据集连接起来,以便获得以下rdd:

val rdd_joined:RDD[((T,W), (U,V))]
实现这一目标最有效的方法是什么?以下是我想到的一些想法

备选案文1:

val m = rdd1.collectAsMap
val rdd_joined = rdd2.map({case ((t,w), u) => ((t,w), u, m.get(t))})
备选案文2:

val distinct_w = rdd2.map({case ((t,w), u) => w}).distinct
val rdd_joined = rdd1.cartesian(distinct_w).join(rdd2)
选项1将收集所有数据以掌握,对吗?因此,如果rdd1很大(在我的例子中,它相对较大,尽管比rdd2小一个数量级),那么这似乎不是一个好的选择。选项2做了一个丑陋的、独特的笛卡尔积,这似乎也是非常低效的。我想到的另一个可能性(但尚未尝试)是执行选项1并广播地图,尽管最好以“智能”方式广播,以便地图的键与
rdd2
的键位于同一位置

以前有人遇到过这种情况吗?我很乐意听听你的想法


谢谢

一个选项是通过收集
rdd1
到驱动程序并将其广播到所有映射程序来执行广播连接;如果操作正确,这将避免对大型
rdd2
RDD:

val rdd1 = sc.parallelize(Seq((1, "A"), (2, "B"), (3, "C")))
val rdd2 = sc.parallelize(Seq(((1, "Z"), 111), ((1, "ZZ"), 111), ((2, "Y"), 222), ((3, "X"), 333)))

val rdd1Broadcast = sc.broadcast(rdd1.collectAsMap())
val joined = rdd2.mapPartitions({ iter =>
  val m = rdd1Broadcast.value
  for {
    ((t, w), u) <- iter
    if m.contains(t)
  } yield ((t, w), (u, m.get(t).get))
}, preservesPartitioning = true)
在我的示例输入中,这两种方法产生相同的结果:

res1: Array[((Int, java.lang.String), (Int, java.lang.String))] = Array(((1,Z),(111,A)), ((1,ZZ),(111,A)), ((2,Y),(222,B)), ((3,X),(333,C)))

第三种方法是重新构造
rdd2
,使
t
成为它的关键,然后执行上面的连接。

另一种方法是创建一个自定义分区器,然后使用zipPartitions来连接您的RDD

import org.apache.spark.HashPartitioner

class RDD2Partitioner(partitions: Int) extends HashPartitioner(partitions) {

  override def getPartition(key: Any): Int = key match {
    case k: Tuple2[Int, String] => super.getPartition(k._1)
    case _ => super.getPartition(key)
  }

}

val numSplits = 8
val rdd1 = sc.parallelize(Seq((1, "A"), (2, "B"), (3, "C"))).partitionBy(new HashPartitioner(numSplits))
val rdd2 = sc.parallelize(Seq(((1, "Z"), 111), ((1, "ZZ"), 111), ((1, "AA"), 123), ((2, "Y"), 222), ((3, "X"), 333))).partitionBy(new RDD2Partitioner(numSplits))

val result = rdd2.zipPartitions(rdd1)(
  (iter2, iter1) => {
    val m = iter1.toMap
    for {
        ((t: Int, w), u) <- iter2
        if m.contains(t)
      } yield ((t, w), (u, m.get(t).get))
  }
).partitionBy(new HashPartitioner(numSplits))

result.glom.collect
import org.apache.spark.HashPartitioner
类RDD2Partitioner(partitions:Int)扩展了HashPartitioner(partitions){
覆盖def getPartition(键:Any):Int=键匹配{
案例k:Tuple2[Int,String]=>super.getPartition(k.\u 1)
case\ux=>super.getPartition(键)
}
}
val numSplits=8
val rdd1=sc.parallelize(Seq((1,“A”),(2,“B”),(3,“C”)).partitionBy(新的HashPartitioner(numSplits))
val rdd2=sc.parallelize(Seq(((1,“Z”),111),((1,“ZZ”),111),((1,“AA”),123),((2,“Y”),222),((3,“X”),333)).partitionBy(新的rdd2分区器(numSplits))
val结果=rdd2.zip分区(rdd1)(
(iter2,iter1)=>{
val m=iter1.toMap
为了{

((t:Int,w),u)我认为你的第二个选择可能是最简单的方法,尽管重新构造rdd2会很方便。我必须了解更多关于mapPartitions函数的工作方式,但这似乎是我一直在寻找的。我也同意我可以重新构造
rdd2
,通过一系列的映射回到我原来想做的事情nted。我将研究这两个选项,看看它们在我的用例中的表现如何。感谢您的建议!对于第一个选项,当我尝试val rdd1Broadcast=sc.broadcast(rdd1.collectAsMap())时,它返回的数据不完整。是否有办法使用collect()而不是collecAsMap()调整第一个选项?
import org.apache.spark.HashPartitioner

class RDD2Partitioner(partitions: Int) extends HashPartitioner(partitions) {

  override def getPartition(key: Any): Int = key match {
    case k: Tuple2[Int, String] => super.getPartition(k._1)
    case _ => super.getPartition(key)
  }

}

val numSplits = 8
val rdd1 = sc.parallelize(Seq((1, "A"), (2, "B"), (3, "C"))).partitionBy(new HashPartitioner(numSplits))
val rdd2 = sc.parallelize(Seq(((1, "Z"), 111), ((1, "ZZ"), 111), ((1, "AA"), 123), ((2, "Y"), 222), ((3, "X"), 333))).partitionBy(new RDD2Partitioner(numSplits))

val result = rdd2.zipPartitions(rdd1)(
  (iter2, iter1) => {
    val m = iter1.toMap
    for {
        ((t: Int, w), u) <- iter2
        if m.contains(t)
      } yield ((t, w), (u, m.get(t).get))
  }
).partitionBy(new HashPartitioner(numSplits))

result.glom.collect