当我需要在RDD中对数据进行分组时,我总是使用reduceByKey
,因为它在对数据进行混洗之前执行地图侧缩减,这通常意味着更少的数据被改组,因此我获得了更好的性能。即使地图侧缩减功能收集所有值并且实际上没有减少数据量,我仍然使用reduceByKey
,因为我假设reduceByKey
的性能永远不会低于groupByKey
{1}}。但是,我想知道这个假设是否正确,或者是否确实存在groupByKey
应该首选的情况?
答案 0 :(得分:15)
我相信climbage和eliasah忽略了问题的其他方面:
如果操作不减少数据量,则必须在语义上等同于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
对象* 为了解决第一个问题,我们需要一个可变数据结构:
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)
reduceByKey
和groupByKey
都使用combineByKey
和不同的合并/合并语义。
我看到的关键区别是groupByKey
将标志(mapSideCombine=false
)传递给shuffle引擎。从问题SPARK-772来看,这是一个提示,当数据大小不会改变时,不会运行mapside combiner。
所以我想说如果您尝试使用reduceByKey
来复制groupByKey
,您可能会看到轻微的性能损失。
答案 2 :(得分:2)
我不会发明轮子,根据代码文档,groupByKey
操作将RDD中每个键的值分组为单个序列,这也允许控制所得键的分区 - 通过Partitioner
传递值对RDD。
此操作可能非常昂贵。如果您要对每个密钥执行聚合(例如总和或平均值)进行分组,则使用aggregateByKey
或reduceByKey
将提供更好的性能。
注意:正如当前实现的那样,groupByKey
必须能够保存内存中任何键的所有键值对。如果某个键的值太多,则可能会导致OOME。
事实上,我更喜欢combineByKey
操作,但如果您不熟悉map-reduce范例,有时很难理解组合器和合并的概念。为此,您可以阅读yahoo map-reduce圣经here,它很好地解释了这个主题。
有关详情,建议您阅读PairRDDFunctions code。