groupByKey是否比reduceByKey更受欢迎

时间:2015-10-19 18:49:05

标签: apache-spark rdd

当我需要在RDD中对数据进行分组时,我总是使用reduceByKey,因为它在对数据进行混洗之前执行地图侧缩减,这通常意味着更少的数据被改组,因此我获得了更好的性能。即使地图侧缩减功能收集所有值并且实际上没有减少数据量,我仍然使用reduceByKey,因为我假设reduceByKey的性能永远不会低于groupByKey {1}}。但是,我想知道这个假设是否正确,或者是否确实存在groupByKey应该首选的情况?

3 个答案:

答案 0 :(得分:15)

我相信climbageeliasah忽略了问题的其他方面:

  • 代码可读性
  • 代码可维护性
  • 代码库大小

如果操作不减少数据量,则必须在语义上等同于GroupByKey。让我们假设我们有RDD[(Int,String)]

import scala.util.Random
Random.setSeed(1)

def randomString = Random.alphanumeric.take(Random.nextInt(10)).mkString("")

val rdd = sc.parallelize((1 to 20).map(_ => (Random.nextInt(5), randomString)))

我们希望连接给定键的所有字符串。使用groupByKey非常简单:

rdd.groupByKey.mapValues(_.mkString(""))

使用reduceByKey的天真解决方案如下所示:

rdd.reduceByKey(_ + _)

它简短易懂,但有两个问题:

  • 效率非常低,因为每次创建一个新的String对象*
  • 表明您执行的操作比实际操作更便宜,特别是如果您只分析DAG或调试字符串

为了解决第一个问题,我们需要一个可变数据结构:

import scala.collection.mutable.StringBuilder

rdd.combineByKey[StringBuilder](
    (s: String) => new StringBuilder(s),
    (sb: StringBuilder, s: String) => sb ++= s,
    (sb1: StringBuilder, sb2: StringBuilder) => sb1.append(sb2)
).mapValues(_.toString)

它仍然暗示了其他正在发生的事情并且非常冗长,特别是如果在您的脚本中重复多次。你当然可以提取匿名函数

val createStringCombiner = (s: String) => new StringBuilder(s)
val mergeStringValue = (sb: StringBuilder, s: String) => sb ++= s
val mergeStringCombiners = (sb1: StringBuilder, sb2: StringBuilder) => 
  sb1.append(sb2)

rdd.combineByKey(createStringCombiner, mergeStringValue, mergeStringCombiners)

但在一天结束时,它仍然需要额外的努力来理解这些代码,增加复杂性并且没有真正的附加价值。我发现特别令人不安的一件事是明确包含可变数据结构。即使Spark几乎处理所有复杂性,也意味着我们不再拥有优雅,引用透明的代码。

我的观点是,如果你真的减少数据量,请务必使用reduceByKey。否则,你会使你的代码更难写,更难以分析并获得任何回报。

注意

这个答案主要针对Scala RDD API。当前的Python实现与其JVM对应物完全不同,并且包括优化,与reduceByKey类似操作的情况相比,它优于朴素groupBy实现。

对于Dataset API,请参阅DataFrame / Dataset groupBy behaviour/optimization

*有关令人信服的示例,请参阅Spark performance for Scala vs Python

答案 1 :(得分:7)

reduceByKeygroupByKey都使用combineByKey和不同的合并/合并语义。

我看到的关键区别是groupByKey将标志(mapSideCombine=false)传递给shuffle引擎。从问题SPARK-772来看,这是一个提示,当数据大小不会改变时,不会运行mapside combiner。

所以我想说如果您尝试使用reduceByKey来复制groupByKey,您可能会看到轻微的性能损失。

答案 2 :(得分:2)

我不会发明轮子,根据代码文档,groupByKey操作将RDD中每个键的值分组为单个序列,这也允许控制所得键的分区 - 通过Partitioner传递值对RDD。

此操作可能非常昂贵。如果您要对每个密钥执行聚合(例如总和或平均值)进行分组,则使用aggregateByKeyreduceByKey将提供更好的性能。

注意:正如当前实现的那样,groupByKey必须能够保存内存中任何键的所有键值对。如果某个键的值太多,则可能会导致OOME。

事实上,我更喜欢combineByKey操作,但如果您不熟悉map-reduce范例,有时很难理解组合器和合并的概念。为此,您可以阅读yahoo map-reduce圣经here,它很好地解释了这个主题。

有关详情,建议您阅读PairRDDFunctions code