如何在Scala Spark Job中的多个键上使用ReduceByKey

时间:2016-09-27 16:27:44

标签: scala apache-spark mapreduce avro

我对火花比较新,我试图同时按多个键对数据进行分组。

我有一些我映射的数据,所以最终看起来像这样:

((K1,K2,K3),(V1,V2))

我的目标是按(K1,K2,K3)分组,并分别将V1和V2加起来:

((K1,K2,K3),(SUM(V1),SUM(V2))

这是我到目前为止的代码:

val filepath  = "file.avro"
val sc = new SparkContext(sparkConf)
val sqlContext = new SQLContext(sc)            
val data = sqlContext.read.avro(filepath)
val dataRDD = data.rdd

val mappedDataRDD = dataRDD.map{
   case (v, w, x, y, z) => ((v,w,x), (y, z))
}.reduceByKey((x,y)=> ???)

所以我正在寻找如何reduceByKey,以便我可以按(v,w,x)键分组并对y和z求和。

2 个答案:

答案 0 :(得分:4)

我认为您正在寻找和应该使用的是aggregateByKey

此方法有两个参数组。第一个参数组取累加器的起始值。第二个参数组有两个函数,

  1. 将事物累积到累加器中的功能。
  2. 组合两个累加器的功能。
  3. 现在您可以按照以下方式使用它,

    val (accZeroY, accZeroZ): (Long, Long) = (0, 0) 
    
    val mappedDataRDD = dataRDD
      .map({
        case (v, w, x, y, z) => ((v,w,x), (y, z))
      })
      .aggregateByKey((accZeroY, accZeroZ))(
        { case ((accY, accZ), (y, z)) =>  (accY + y, accZ + z) }
        { case ((accY1, accZ1), (accY2, accZ2)) => (accY1 + accY2, accZ1 + accZ2) }
      )
    

    正如您应该观察到的那样,第二个参数组中的两个函数在这种情况下实际上是相同的。只有当type of the needed accumulationkey-value-RDDPairRDD中的值类型相同时,才有可能实现此目的。

    在这种情况下,您也可以使用reduceByKey,您可以将其视为aggregateByKey,并且函数参数传递的函数相同,

    val mappedDataRDD = dataRDD
      .map({
        case (v, w, x, y, z) => ((v,w,x), (y, z))
      })
      .reduceByKey(
        { case ((accY, accZ), (y, z)) =>  (accY + y, accZ + z) }
      )
    

    但在我看来,should NOT使用reduceBykey。我建议使用aggregateByKey的原因是因为在大型数据集上积累值有时会产生超出类型范围的结果。

    例如,在您的情况下,我怀疑您的(x, y)实际上是(Int, Int),并且您希望使用(v, w, x)作为关键积累它。但是,无论何时大量添加Int,请记住,结果最终可能会超过Int可以处理的结果。

    所以...你会希望你的积累类型更像(Int, Int) (Long, Long) reduceByKey,而aggregateByKey不允许你这样做。所以...我会说,也许你正在寻找并应该使用To

答案 1 :(得分:1)

你也可以使用reduceByKey,你只需要小心你想要的东西。我简化了这个例子,但它暴露了你想要的东西。

val rdd = sc.parallelize(List(
  (1, 2, 1, 1, 1), 
  (1, 2, 1, 2, 2),   
  (1, 3, 2, 4, 4)))

rdd.map {
  case (k1, k2, k3, v1, v2) => ((k1, k2, k3), (v1, v2))
}.reduceByKey {
  // You receive two values which are actually tuples, so we treat them like that.
  case ((x1, y1), (x2, y2)) => (x1 + x2, y1 + y2)
}.collect()
//res0: Array[((Int, Int), (Int, Int))] = Array(((1,2,1),(3,3)), ((1,3,2),(4,4)))