[英]Spark RDD: multiple reducebykey or just once
我有如下代码:
// make a rd according to an id
def makeRDD(id:Int, data:RDD[(VertexId, Double)]):RDD[(Long, Double)] = { ... }
val data:RDD[(VertexId, Double)] = ... // loading from hdfs
val idList = (1 to 100)
val rst1 = idList.map(id => makeRDD(id, data)).reduce(_ union _).reduceByKey(_+_)
val rst2 = idList.map(id => makeRDD(id, data)).reduce((l,r) => (l union r).reduceByKey(_+_))
rst1和rst2获得样本结果。 我以为rst1需要更多的内存(100次),但只有一个reduceByKey转换; 但是,rst2需要较少的内存,但需要更多的reduceByKey转换(99次)。 那么,这是时间与空间的权衡游戏吗?
我的问题是:我上面的分析是正确的,还是Spark对待以内部相同的方式转换动作?
PS:rst1联合所有子rdd然后reduceByKey,其中reduceByKey 在 reduce 之外 。 rst2 reduceByKey一一对应,其中reduceByKey 在 reduce 里面 。
长话短说,这两种解决方案效率都相对较低,但第二种解决方案比第一种解决方案差。
让我们从回答最后一个问题开始。 对于低级RDD API,只有两种类型的全局自动优化(代替):
ShuffleMapStage
其他所有内容几乎都是定义DAG的顺序转换。 这与限制性更高的高级Dataset
( DataFrame
)API DataFrame
,后者对转换进行了特定假设并执行了执行计划的全局优化。
关于您的代码。 第一个解决方案的最大问题是,当您应用迭代union
时,沿袭越来越大。 它使某些事情(例如,故障恢复成本很高)以及由于RDD是递归定义的,可能会因StackOverflow
异常而失败。 较不严重的副作用是分区数量的增加,但在随后的减少中似乎没有得到补偿*。 由于较长的RDD沿袭,您会在我对Stackoverflow的回答中找到更详细的解释,但是您真正需要的是这样的单个union
:
sc.union(idList.map(id => makeRDD(id, data))).reduceByKey(_+_)
假设您应用了真正的归约功能,这实际上是一个最佳解决方案。
第二种解决方案显然也遇到了同样的问题,但是情况变得更糟。 尽管第一种方法只需要两个阶段并进行一次混洗,但这需要为每个RDD
进行混洗。 由于分区的数量在增加,并且您使用默认的HashPartitioner
每条数据都必须多次写入磁盘,并且很可能在网络上多次洗牌。 忽略低级计算,每条记录会被洗净O(N)次,其中N是您合并的RDD数。
关于内存使用情况,在不了解更多数据分布的情况下并不明显,但是在最坏的情况下,第二种方法可能表现出明显更差的行为。
如果+
在恒定的空间上工作,则减少的唯一要求是一个哈希图,用于存储地图端合并的结果。 由于分区是作为数据流处理的,而没有将完整的内容读取到内存中,因此这意味着每个任务的总内存大小将与唯一键的数量成正比,而不与数据量成正比。 由于第二种方法需要执行更多任务,因此总体内存使用量将高于第一种情况。 平均而言,由于数据是部分组织的,因此可能会稍微好一些,但不太可能补偿额外的费用。
*如果您想了解它如何影响整体性能,您可以看到使用连接时Spark迭代时间呈指数增长。这是一个稍有不同的问题,但是应该让您了解为什么控制分区数很重要。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.