【问题标题】:How in Spark application create CSV file from DataFrame (Scala)?Spark 应用程序中如何从 DataFrame (Scala) 创建 CSV 文件?
【发布时间】:2019-01-20 23:14:25
【问题描述】:

我的下一个问题并不新鲜,但我想了解如何一步一步完成。

在 Spark 应用程序中,我创建了 DataFrame。让我们称之为df。 Spark 版本:2.4.0

val df: DataFrame  = Seq(
    ("Alex", "2018-01-01 00:00:00", "2018-02-01 00:00:00", "OUT"),
    ("Bob", "2018-02-01 00:00:00", "2018-02-05 00:00:00", "IN"),
    ("Mark", "2018-02-01 00:00:00", "2018-03-01 00:00:00", "IN"),
    ("Mark", "2018-05-01 00:00:00", "2018-08-01 00:00:00", "OUT"),
    ("Meggy", "2018-02-01 00:00:00", "2018-02-01 00:00:00", "OUT")
).toDF("NAME", "START_DATE", "END_DATE", "STATUS")

如何从这个DataFrame创建.csv文件并将csv文件放到服务器的特定文件夹中?

例如,这段代码是否正确?我注意到有些人使用coalescerepartition 来完成这项任务。但我不明白在我的情况下哪一个会更好。

union.write
  .format("com.databricks.spark.csv")
  .option("header", "true")
  .save("/home/reports/")

当我尝试使用下一个代码时,它会引发 ERROR:

org.apache.hadoop.security.AccessControlException: Permission denied: user=root, access=WRITE, inode="/home/reports/_temporary/0":hdfs:hdfs:drwxr-xr-x 

我以root 用户身份运行 Spark 应用程序。 reportsroot 用户使用下一条命令创建的文件夹:

mkdir -m 777 reports

好像只有hdfs用户可以写文件。

【问题讨论】:

  • 在 Spark 2.* 中,您可以使用语法 dataFrame.option("header", "true").csv("/home/reports/")。但是你不应该混淆本地文件系统和 HDFS。您指定的路径是 HDFS 上的路径。您的用户无权写入该位置。如果您想写入服务器上的本地目录,您应该首先使用val dataToSave = dataFrame.collect() 收集数据。如果这样做,与 DataFrame 相关的所有数据都将转到 Spark 主节点。因此,请确保您有足够的内存。之后,您将能够使用标准 Scala/Java IO API 保存数据。
  • 不,我不是这个意思。您可以使用dataToSave.write.csv(path) 将数据保存到 HDFS 或 S3。如果要将 DataFrame 保存到服务器上的某个位置。你应该做val dataToSave = dataFrame.collect()。之后,数据将包含在 Spark 主节点的内存中。然后你可以使用PrintWriterFileWriter来保存数据。
  • 如果你决定在 DataFrame 上使用collect 方法,你会得到Array[Row]。之后你可以做val linesToSave: Array[String] = dataToSave.map(_.toSeq.mkString(";"))。此外,您可以查看 au.com.bytecode.opencsv.CSVWriter 以从 Array[Row] 创建 csv 文件。
  • 其实,最简单的方法是使用第一条评论页面中描述的方法。您可以将文件写入 HDFS 上的某个临时位置(例如:/tmp),并在作业完成后将其复制/移动到本地目录。

标签: scala apache-spark


【解决方案1】:

我相信您对 Spark 的行为方式感到困惑,我建议您先阅读官方文档和/或一些教程。
不过,我希望这能回答您的问题。

此代码会将 DataFrame 保存为本地文件系统上的 SINGLE CSV 文件
已使用 Spark 2.4.0Scala 2.12.8Ubuntu 18.04 笔记本电脑上。

import org.apache.spark.sql.SparkSession

val spark =
  SparkSession
    .builder
    .master("local[*]")
    .appName("CSV Writter Test")
    .getOrCreate()
import spark.implicits._

val df =
  Seq(
    ("Alex", "2018-01-01 00:00:00", "2018-02-01 00:00:00", "OUT"),
    ("Bob", "2018-02-01 00:00:00", "2018-02-05 00:00:00", "IN"),
    ("Mark", "2018-02-01 00:00:00", "2018-03-01 00:00:00", "IN"),
    ("Mark", "2018-05-01 00:00:00", "2018-08-01 00:00:00", "OUT"),
    ("Meggy", "2018-02-01 00:00:00", "2018-02-01 00:00:00", "OUT")
  ).toDF("NAME", "START_DATE", "END_DATE", "STATUS")

df.printSchema
// root
//  |-- NAME: string (nullable = true)
//  |-- START_DATE: string (nullable = true)
//  |-- END_DATE: string (nullable = true)
//  |-- STATUS: string (nullable = true)

df.coalesce(numPartitions = 1)
  .write
  .option(key = "header", value = "true")
  .option(key = "sep", value = ",")
  .option(key = "encoding", value = "UTF-8")
  .option(key = "compresion", value = "none")
  .mode(saveMode = "OVERWRITE")
  .csv(path = "file:///home/balmungsan/dailyReport/") // Change the path. Note there are 3 /, the first two are for the file protocol, the third one is for the root folder.

spark.stop()

现在,让我们检查保存的文件。

balmungsan@BalmungSan:dailyReport $ pwd
/home/balmungsan/dailyReport

balmungsan@BalmungSan:dailyReport $ ls
part-00000-53a11fca-7112-497c-bee4-984d4ea8bbdd-c000.csv  _SUCCESS

balmungsan@BalmungSan:dailyReport $ cat part-00000-53a11fca-7112-497c-bee4-984d4ea8bbdd-c000.csv 
NAME,START_DATE,END_DATE,STATUS
Alex,2018-01-01 00:00:00,2018-02-01 00:00:00,OUT
Bob,2018-02-01 00:00:00,2018-02-05 00:00:00,IN
Mark,2018-02-01 00:00:00,2018-03-01 00:00:00,IN
Mark,2018-05-01 00:00:00,2018-08-01 00:00:00,OUT
Meggy,2018-02-01 00:00:00,2018-02-01 00:00:00,OUT

_SUCCESS 文件的存在表示写入成功。

重要提示:

  • 您需要指定file:// 协议以保存到本地文件系统,而不是HDFS
  • 路径指定保存文件分区的文件夹的名称,而不是文件的名称,在该文件夹中,每个分区将有一个文件。如果你想用Spark再次读取这样的文件,那么你只需要指定文件夹,Spark就会理解分区文件。如果没有,我建议在之后重命名文件 - 据我所知,没有办法从 Spark 控制名称。
  • 如果 df 太大而无法仅容纳一个节点的内存,则作业将失败。
  • 如果您以分布式方式运行它(例如使用主纱线),则文件将不会保存在主节点中,而是保存在其中一个从节点中。如果你真的需要它在主节点中,那么你可以收集它并按照 Dmitry 的建议用普通的 Scala 编写它。

【讨论】:

  • 您好!感谢您的详细回答。 Spark无法更改csv文件的名称确实是一个问题。未来 DataFrame 可能会很大。所以你建议我使用更多内存或更多节点,对吧?你能更详细地描述你关于收集的最后一步吗? Dmitry 建议使用 au.com.bytecode.opencsv.CSVWriter 库从 Array[Row] 创建 csv 文件,但我仍然找不到任何好的示例。
  • 如果DataFrame 真的很大,我建议使用更多节点 (水平缩放总是比垂直更好)。同样,如果DataFrame 很大,那么collect 可能会使应用程序崩溃,甚至写入单个文件也没有任何意义。 (除非最终结果只是一份小报告)。 Spark之所以不能改名,是因为如上所述,由于通常会有多个part文件,那么文件夹就成为分布式文件系统中的文件标识符。如果您愿意,可以邀请我参加 SO 聊天,以讨论有关您的用例的更多信息。
猜你喜欢
  • 2020-06-01
  • 2015-11-30
  • 2020-08-09
  • 2016-08-14
  • 2018-11-07
  • 1970-01-01
  • 2016-09-13
  • 2020-09-22
  • 1970-01-01
相关资源
最近更新 更多