Spark聚合函数如何 - aggregateByKey有效吗?

时间:2014-07-17 13:14:18

标签: apache-spark distributed-computing

假设我在3个节点上有一个分发系统,我的数据在这些节点之间分配。例如,我有一个test.csv文件,它存在于所有3个节点上,它包含2列:

**row   | id,  c.**
---------------
row1  | k1 , c1  
row2  | k1 , c2  
row3  | k1 , c3  
row4  | k2 , c4  
row5  | k2 , c5  
row6  | k2 , c6  
row7  | k3 , c7  
row8  | k3 , c8  
row9  | k3 , c9  
row10 | k4 , c10   
row11 | k4 , c11  
row12 | k4 , c12 

然后我使用SparkContext.textFile将文件读出为rdd等等。据我所知,每个spark worker节点都会读取文件中的一部分。所以现在让我们说每个节点都会存储:

  • 节点1:第1~4行
  • 节点2:第5~8行
  • 节点3:第9~12行

我的问题是,让我们说我想对这些数据进行计算,我需要将一个键组合在一起,因此键值对将是[k1 [{k1 c1} {k1 c2} {k1 c3}]]..所以上。

有一个名为groupByKey()的函数,使用起来非常昂贵,建议使用aggregateByKey()。所以我想知道groupByKey()aggregateByKey()如何在幕后工作?有人可以使用我上面提供的例子来解释吗?在洗牌后,每个节点上的行都在哪里?

2 个答案:

答案 0 :(得分:57)

aggregateByKey()与reduceByKey完全不同。会发生什么是reduceByKey是aggregateByKey的一个特例。

aggregateByKey()将组合特定键的值,此类组合的结果可以是您指定的任何对象。您必须指定如何在一个分区(在同一节点中执行)中组合值("添加")以及如何组合来自不同分区(可能在不同节点中)的结果。 reduceByKey是一种特殊情况,在某种意义上,组合的结果(例如,和)与值的类型相同,并且当从不同分区组合时的操作也与组合内部的值时的操作相同。分区。

一个例子: 想象一下,你有一对配对列表。你并行化它:

val pairs = sc.parallelize(Array(("a", 3), ("a", 1), ("b", 7), ("a", 5)))

现在你想要"结合"他们通过钥匙产生一笔钱。在这种情况下,reduceByKey和aggregateByKey是相同的:

val resReduce = pairs.reduceByKey(_ + _) //the same operation for everything
resReduce.collect
res3: Array[(String, Int)] = Array((b,7), (a,9))

//0 is initial value, _+_ inside partition, _+_ between partitions
val resAgg = pairs.aggregateByKey(0)(_+_,_+_)
resAgg.collect
res4: Array[(String, Int)] = Array((b,7), (a,9))

现在,假设您希望聚合是一组值,这是一个不同的类型,值是整数(整数之和也是整数):

import scala.collection.mutable.HashSet
//the initial value is a void Set. Adding an element to a set is the first
//_+_ Join two sets is the  _++_
val sets = pairs.aggregateByKey(new HashSet[Int])(_+_, _++_)
sets.collect
res5: Array[(String, scala.collection.mutable.HashSet[Int])]  =Array((b,Set(7)), (a,Set(1, 5, 3)))

答案 1 :(得分:46)

aggregateByKey()几乎与reduceByKey()相同(在幕后调用combineByKey()),除了您为aggregateByKey()提供起始值。大多数人都熟悉reduceByKey(),所以我会在解释中使用它。

reduceByKey()更好的原因是因为它使用了一个叫做组合器的 MapReduce 功能。像+*这样的函数可以这种方式使用,因为它被调用的元素的顺序并不重要。这允许Spark开始"减少"具有相同密钥的值,即使它们还不在同一个分区中。

另一方面groupByKey()为您提供了更多功能,因为您编写了一个带有Iterable的函数,这意味着您甚至可以将所有元素拉入数组。然而,这是低效的,因为要使它工作,整套(K,V,)对必须在一个分区中。

在reduce类型操作上移动数据的步骤通常称为 shuffle ,在最简单的级别上,数据被分区到每个节点(通常使用散列分区程序),然后在每个节点上排序。