有没有办法重写Spark RDD distinct使用mapPartitions而不是distinct?

时间:2015-06-26 21:00:05

标签: scala apache-spark distinct shuffle rdd

我的RDD太大而不能一致地执行不同的语句而没有虚假错误(例如,SparkException阶段失败4次,ExecutorLostFailure,HDFS文件系统关闭,执行器失败的最大数量达到,因为SparkContext关闭而取消阶段,等等。)

我正在尝试计算特定列中的不同ID,例如:

print(myRDD.map(a => a._2._1._2).distinct.count())

是否有一种简单,一致,不太随机密集的方式来执行上述命令,可能使用mapPartitions,reduceByKey,flatMap或其他使用较少shuffle而不是不同的命令?

另见What are the Spark transformations that causes a Shuffle?

2 个答案:

答案 0 :(得分:2)

最好弄清楚是否存在另一个潜在的问题,但下面会做你想做的事情......而不是围绕这样做,但听起来它会适合你的账单:

myRDD.map(a => (a._2._1._2, a._2._1._2))
  .aggregateByKey(Set[YourType]())((agg, value) => agg + value, (agg1, agg2) => agg1 ++ agg2) 
  .keys
  .count

或者甚至这似乎有效,但它不是联想和可交换的。它起作用的原因是Spark的内部工作原理......但我可能会错过一个案例......所以虽然更简单,但我不确定我是否相信它:

myRDD.map(a => (a._2._1._2, a._2._1._2))
  .aggregateByKey(YourTypeDefault)((x,y)=>y, (x,y)=>x)
  .keys.count

答案 1 :(得分:0)

我认为有2种可能的解决方案:

  1. 使用 reduceByKey
  2. 具有 mapPartitions

让我们用一个例子看一下它们。

我有一个100.000电影评分的数据集,格式为(idUser,(idMovie,rating))。假设我们想知道有多少不同的用户对电影进行了评分:

首先让我们使用与众不同的

val numUsers = rddSplitted.keys.distinct()
println(s"numUsers is ${numUsers.count()}")
println("*******toDebugString of rddSplitted.keys.distinct*******")
println(numUsers.toDebugString)

我们将得到以下结果:

numUsers is 943

*******toDebugString of rddSplitted.keys.distinct*******
(2) MapPartitionsRDD[6] at distinct at MovieSimilaritiesRicImproved.scala:98 []
 |  ShuffledRDD[5] at distinct at MovieSimilaritiesRicImproved.scala:98 []
 +-(2) MapPartitionsRDD[4] at distinct at MovieSimilaritiesRicImproved.scala:98 []
    |  MapPartitionsRDD[3] at keys at MovieSimilaritiesRicImproved.scala:98 []
    |  MapPartitionsRDD[2] at map at MovieSimilaritiesRicImproved.scala:94 []
    |  C:/spark/ricardoExercises/ml-100k/u.data MapPartitionsRDD[1] at textFile at MovieSimilaritiesRicImproved.scala:90 []
    |  C:/spark/ricardoExercises/ml-100k/u.data HadoopRDD[0] at textFile at MovieSimilaritiesRicImproved.scala:90 []

使用 toDebugString 函数,我们可以更好地分析RDD发生的情况。

现在,让我们使用 reduceByKey ,例如,计算每个用户投票的次数并同时获得不同用户的数量:

val numUsers2 = rddSplitted.map(x => (x._1, 1)).reduceByKey({case (a, b) => a })
println(s"numUsers is ${numUsers2.count()}")
println("*******toDebugString of rddSplitted.map(x => (x._1, 1)).reduceByKey(_+_)*******")
println(numUsers2.toDebugString)

我们现在将获得以下结果:

numUsers is 943

*******toDebugString of rddSplitted.map(x => (x._1, 1)).reduceByKey(_+_)*******
(2) ShuffledRDD[4] at reduceByKey at MovieSimilaritiesRicImproved.scala:104 []
 +-(2) MapPartitionsRDD[3] at map at MovieSimilaritiesRicImproved.scala:104 []
    |  MapPartitionsRDD[2] at map at MovieSimilaritiesRicImproved.scala:94 []
    |  C:/spark/ricardoExercises/ml-100k/u.data MapPartitionsRDD[1] at textFile at MovieSimilaritiesRicImproved.scala:90 []
    |  C:/spark/ricardoExercises/ml-100k/u.data HadoopRDD[0] at textFile at MovieSimilaritiesRicImproved.scala:90 []

分析RDD的产生,我们可以看到 reduceByKey 以比以前的 distinct 更有效的方式执行相同的动作。

最后,让我们使用 mapPartitions 。主要目标是尝试首先区分数据集每个分区中的用户,然后获得最终的不同用户。

val a1 = rddSplitted.map(x => (x._1))
println(s"Number of elements in a1: ${a1.count}")
val a2 = a1.mapPartitions(x => x.toList.distinct.toIterator)
println(s"Number of elements in a2: ${a2.count}")
val a3 = a2.distinct()
println("There are "+ a3.count()+" different users")
println("*******toDebugString of map(x => (x._1)).mapPartitions(x => x.toList.distinct.toIterator).distinct *******")
println(a3.toDebugString)

我们将获得以下信息:

Number of elements in a1: 100000
Number of elements in a2: 1709
There are 943 different users

*******toDebugString of map(x => (x._1)).mapPartitions(x => x.toList.distinct.toIterator).distinct *******
(2) MapPartitionsRDD[7] at distinct at MovieSimilaritiesRicImproved.scala:124 []
 |  ShuffledRDD[6] at distinct at MovieSimilaritiesRicImproved.scala:124 []
 +-(2) MapPartitionsRDD[5] at distinct at MovieSimilaritiesRicImproved.scala:124 []
    |  MapPartitionsRDD[4] at mapPartitions at MovieSimilaritiesRicImproved.scala:122 []
    |  MapPartitionsRDD[3] at map at MovieSimilaritiesRicImproved.scala:120 []
    |  MapPartitionsRDD[2] at map at MovieSimilaritiesRicImproved.scala:94 []
    |  C:/spark/ricardoExercises/ml-100k/u.data MapPartitionsRDD[1] at textFile at MovieSimilaritiesRicImproved.scala:90 []
    |  C:/spark/ricardoExercises/ml-100k/u.data HadoopRDD[0] at textFile at MovieSimilaritiesRicImproved.scala:90 []

我们现在可以看到, mapPartition 首先在数据集的每个分区中获得了不同的用户数,从而将实例数从100,000缩短到了1,709,而没有进行任何改组。然后,使用少得多的数据量,就可以在整个RDD上执行 distinct ,而不必担心数据洗牌,并且可以更快地获得结果。

我建议将最后一个建议与 mapPartitions 而不是 reduceByKey 一起使用,因为它管理的数据量较小。另一个解决方案可能是同时使用这两个函数,如前所述,先使用 mapPartitions ,然后再使用 reduceByKey 代替 distinct 以前。