多个小型RDD的有效结合

时间:2016-10-31 19:12:58

标签: scala apache-spark

我有一系列多个小文件(~1-8KB),我想计算这些文件的字数。具体来说,序列I具有files: Seq[String],其中序列的每个字符串是每个文件的路径。我根据该序列计算总字数的方法是:

val totalWordCount = sc.union(
      files.map(path => sc.textFile(path))
    ).flatMap(line => line.split(" "))
      .map((_,1))
      // I use a hash partitioner for better performance of reduceByKey
      .partitionBy(new HashPartitioner(numPartitions))
      .reduceByKey(_ + _)

我遇到的问题是,即使我有超过10000个小文件并使用上述技术,当我增加分区时执行时间也会增加。那是为什么?

请注意,我不能将这些小文件从头开始合并为一个输入,而是需要输入字符串序列。

1 个答案:

答案 0 :(得分:3)

为什么它很慢

sc.textFile未针对此案例进行优化。请记住,最佳分区大小约为100 MB,而现在,sc.union RDD每个文件获得一个分区 - < 8k。火花开销绝对会占据你在这个范例中所做的任何事情。

你提到过#34;增加分区"在你的问题中,但我想你可能想要减少分区的数量。我不确定numPartitions来自哪里,但这应该是大致的总数据大小/ 100MB。您的.partitionBy步骤正在执行完全洗牌,因此原始的太多分区RDD仍然会有很多开销,但它可能会在下游执行得更好。

尝试

的另一个执行模型

在这里尝试一些其他的事情:在工会上没有洗牌合并:

val optimalNPartitions = ??? // calculate total size / 100MB here
val totalWordCount = sc.union(files.map(path => sc.textFile(path)))
  .flatMap(line => line.split(" "))
  .coalesce(optimalNPartitions, shuffle = false) // try with shuf = true as well!
  .map((_,1))
  .reduceByKey(_ + _)

最后一个注释

虽然您说您正在分区到新的散列分区程序以使reduceByKey更有效,但实际上这是错误的。

让我们来看看这两个模型。首先,你有一个:partitionBy后跟reduceByKey。分区步骤将对新的散列分区程序进行完全洗牌 - 所有数据都需要在网络中移动。当你调用reduce时,所有类似的键都已经在同一个地方,因此不需要进行随机播放。

其次,请忽略partitionBy,然后致电reduceByKey。在这个模型中,你进入reduce没有分区,所以你必须洗牌。但是在你洗牌之前,你要在本地减少 - 如果你有“#34; dog"在一个分区上100次,您需要将("dog", 100)而不是("dog", 1)洗牌100次。看看我会去哪里? Reduce实际上只需要一个部分shuffle,其大小由键的稀疏性决定(如果你只有一些独特的键,很少被洗牌。如果一切都是独特的,那么一切都是洗牌的)。

显然,模型2是我们想要的。摆脱partitionBy