Spark数据帧写入方法编写了许多小文件

时间:2017-06-09 13:40:46

标签: scala apache-spark

我有一个相当简单的工作,将日志文件转换为镶木地板。它正在处理1.1TB的数据(分为64MB - 128MB文件 - 我们的块大小为128MB),大约有12000个文件。

工作如下:

 val events = spark.sparkContext
  .textFile(s"$stream/$sourcetype")
  .map(_.split(" \\|\\| ").toList)
  .collect{case List(date, y, "Event") => MyEvent(date, y, "Event")}
  .toDF()

df.write.mode(SaveMode.Append).partitionBy("date").parquet(s"$path")

它使用通用架构收集事件,转换为DataFrame,然后写出镶木地板。

我遇到的问题是,这可能会在HDFS群集上造成一些IO爆炸,因为它正在尝试创建这么多小文件。

理想情况下,我想在分区'date'中只创建一些镶木地板文件。

控制此问题的最佳方法是什么?是通过使用'coalesce()'吗?

这将如何影响给定分区中创建的文件数量?它取决于我在Spark中使用了多少执行程序? (目前设定为100)。

6 个答案:

答案 0 :(得分:10)

您必须重新设置DataFrame以匹配DataFrameWriter的分区

试试这个:

df
.repartition($"date")
.write.mode(SaveMode.Append)
.partitionBy("date")
.parquet(s"$path")

答案 1 :(得分:3)

最简单的解决方案是通过以下方式替换您的实际分区:

df
 .repartition(to_date($"date"))
 .write.mode(SaveMode.Append)
 .partitionBy("date")
 .parquet(s"$path")

您还可以为DataFrame使用更精确的分区,即日期和小时范围。然后你可能不那么精确的作家。 这实际上取决于数据量。

您可以通过分区DataFrame和使用partition by子句写入来减少熵。

答案 2 :(得分:2)

在Python中,您可以将Raphael's Roth answer重写为:

(df
  .repartition("date")
  .write.mode("append")
  .partitionBy("date")
  .parquet("{path}".format(path=path)))

您还可以考虑向.repartition添加更多列,以避免出现很大的分区问题:

(df
  .repartition("date", another_column, yet_another_colum)
  .write.mode("append")
  .partitionBy("date)
  .parquet("{path}".format(path=path)))

答案 3 :(得分:1)

我遇到了同样的问题,我可以使用coalesce解决了我的问题。

df
  .coalesce(3) // number of parts/files 
  .write.mode(SaveMode.Append)
  .parquet(s"$path")

有关使用coalescerepartition的详细信息,请参阅以下spark: coalesce or repartition

答案 4 :(得分:0)

从这里复制我的答案:https://stackoverflow.com/a/53620268/171916

这对我很好:

data.repartition(n, "key").write.partitionBy("key").parquet("/location")

它在每个输出分区(目录)中产生N个文件,并且(偶然地)比使用coalesce (同样,在我的数据集上)要快,仅比重新分区要快在输出上。

如果您使用的是S3,我还建议您在本地驱动器上进行所有操作(Spark在写出过程中会执行大量文件创建/重命名/删除操作),一旦全部解决,请使用hadoop FileUtil(或者只是aws cli)复制所有内容:

import java.net.URI
import org.apache.hadoop.fs.{FileSystem, FileUtil, Path}
// ...
  def copy(
          in : String,
          out : String,
          sparkSession: SparkSession
          ) = {
    FileUtil.copy(
      FileSystem.get(new URI(in), sparkSession.sparkContext.hadoopConfiguration),
      new Path(in),
      FileSystem.get(new URI(out), sparkSession.sparkContext.hadoopConfiguration),
      new Path(out),
      false,
      sparkSession.sparkContext.hadoopConfiguration
    )
  }

答案 5 :(得分:0)

如何尝试将这样的脚本作为地图作业来将所有镶木地板文件合并为一个:

$ hadoop jar /usr/hdp/2.3.2.0-2950/hadoop-mapreduce/hadoop-streaming-2.7.1.2.3.2.0-2950.jar \                    -Dmapred.reduce.tasks = 1 \                    -输入“ / hdfs / input / dir” \                    -输出“ / hdfs / output / dir” \                    -地图猫                    还原猫