J'utilise https://github.com/databricks/spark-csv , j'essaie d'écrire un seul fichier CSV, mais je ne le peux pas, un dossier est créé.
Besoin d'une fonction Scala qui prendra les paramètres tels que chemin et nom de fichier et écrira ce fichier CSV.
Il crée un dossier avec plusieurs fichiers, car chaque partition est enregistrée individuellement. Si vous avez besoin d'un seul fichier de sortie (toujours dans un dossier), vous pouvez repartition
(préférable si les données en amont sont volumineuses, mais nécessitent une lecture aléatoire):
df
.repartition(1)
.write.format("com.databricks.spark.csv")
.option("header", "true")
.save("mydata.csv")
ou coalesce
:
df
.coalesce(1)
.write.format("com.databricks.spark.csv")
.option("header", "true")
.save("mydata.csv")
trame de données avant de sauvegarder:
Toutes les données seront écrites sur mydata.csv/part-00000
. Avant d’utiliser cette option , assurez-vous de bien comprendre ce qui se passe et quel est le coût du transfert de toutes les données à un seul opérateur . Si vous utilisez un système de fichiers distribué avec réplication, les données seront transférées plusieurs fois. Elles seront d'abord extraites vers un seul opérateur, puis distribuées sur des nœuds de stockage.
Sinon, vous pouvez laisser votre code tel quel et utiliser des outils d'usage général tels que cat
ou HDFS getmerge
pour simplement fusionner toutes les parties par la suite.
Si vous utilisez Spark avec HDFS, j'ai résolu le problème en écrivant des fichiers csv normalement et en exploitant HDFS pour effectuer la fusion. Je le fais dans Spark (1.6) directement:
import org.Apache.hadoop.conf.Configuration
import org.Apache.hadoop.fs._
def merge(srcPath: String, dstPath: String): Unit = {
val hadoopConfig = new Configuration()
val hdfs = FileSystem.get(hadoopConfig)
FileUtil.copyMerge(hdfs, new Path(srcPath), hdfs, new Path(dstPath), true, hadoopConfig, null)
// the "true" setting deletes the source files once they are merged into the new output
}
val newData = << create your dataframe >>
val outputfile = "/user/feeds/project/outputs/subject"
var filename = "myinsights"
var outputFileName = outputfile + "/temp_" + filename
var mergedFileName = outputfile + "/merged_" + filename
var mergeFindGlob = outputFileName
newData.write
.format("com.databricks.spark.csv")
.option("header", "false")
.mode("overwrite")
.save(outputFileName)
merge(mergeFindGlob, mergedFileName )
newData.unpersist()
Je ne me souviens pas où j'ai appris cette astuce, mais cela pourrait fonctionner pour vous.
Je suis peut-être un peu en retard au jeu ici, mais utiliser coalesce(1)
ou repartition(1)
peut fonctionner pour de petits ensembles de données, mais les grands ensembles de données seraient tous projetés dans une partition sur un nœud. Cela risque de provoquer des erreurs dans le MOO ou, au mieux, de procéder lentement.
Je vous suggère fortement d'utiliser la fonction FileUtil.copyMerge()
de l'API Hadoop. Cela va fusionner les sorties dans un seul fichier.
EDIT - Cela amène efficacement les données au pilote plutôt qu’à un nœud exécuteur. Coalesce()
serait acceptable si un seul exécuteur a plus de RAM à utiliser que le pilote.
EDIT 2: copyMerge()
est en cours de suppression dans Hadoop 3.0. Voir l'article suivant sur le dépassement de pile pour plus d'informations sur l'utilisation de la version la plus récente: Hadoop comment copier CopyMerge dans Hadoop 3.
Si vous utilisez Databricks et pouvez insérer toutes les données dans RAM sur un opérateur (et donc utiliser .coalesce(1)
), vous pouvez utiliser dbfs pour rechercher et déplacer le fichier CSV résultant:
val fileprefix= "/mnt/aws/path/file-prefix"
dataset
.coalesce(1)
.write
//.mode("overwrite") // I usually don't use this, but you may want to.
.option("header", "true")
.option("delimiter","\t")
.csv(fileprefix+".tmp")
val partition_path = dbutils.fs.ls(fileprefix+".tmp/")
.filter(file=>file.name.endsWith(".csv"))(0).path
dbutils.fs.cp(partition_path,fileprefix+".tab")
dbutils.fs.rm(fileprefix+".tmp",recurse=true)
Si votre fichier ne rentre pas dans RAM sur le poste de travail, vous pouvez envisager suggestion de chaotic3quilibrium d'utiliser FileUtils.copyMerge () . Je n'ai pas fait cela et je ne sais pas encore si c'est possible ou non, par exemple sur S3.
Cette réponse est construite sur les réponses précédentes à cette question ainsi que sur mes propres tests de l'extrait de code fourni. Je l'ai posté à l'origine à Databricks et je le republie ici.
La meilleure documentation que j'ai trouvée sur l'option récursive de rm de dbfs est sur n forum Databricks .
Une solution qui fonctionne pour S3 modifié par Minkymorgan.
Passez simplement le chemin du répertoire partitionné temporaire (avec un nom différent du chemin final) en tant que srcPath
et le dernier fichier csv/txt en tant que destPath
Indiquez également deleteSource
si vous souhaitez supprimer le répertoire d'origine.
/**
* Merges multiple partitions of spark text file output into single file.
* @param srcPath source directory of partitioned files
* @param dstPath output path of individual path
* @param deleteSource whether or not to delete source directory after merging
* @param spark sparkSession
*/
def mergeTextFiles(srcPath: String, dstPath: String, deleteSource: Boolean): Unit = {
import org.Apache.hadoop.fs.FileUtil
import Java.net.URI
val config = spark.sparkContext.hadoopConfiguration
val fs: FileSystem = FileSystem.get(new URI(srcPath), config)
FileUtil.copyMerge(
fs, new Path(srcPath), fs, new Path(dstPath), deleteSource, config, null
)
}
vous pouvez utiliser rdd.coalesce(1, true).saveAsTextFile(path)
il stockera les données sous forme de fichier unique dans chemin/partie-00000
repartitionner/fusionner sur 1 partition avant de sauvegarder (vous obtiendrez tout de même un dossier mais il contiendrait un fichier partiel)
l'API df.write()
de spark va créer plusieurs fichiers de pièce dans un chemin donné ... pour forcer spark écrire un seul fichier de pièce, utilisez df.coalesce(1).write.csv(...)
au lieu de df.repartition(1).write.csv(...)
comme coalesce est un transformation étroite alors que la répartition est une transformation large, voir Spark - repartition () vs coalesce ()
df.coalesce(1).write.csv(filepath,header=True)
créera un dossier dans un chemin de fichier donné avec un fichier part-0001-...-c000.csv
cat filepath/part-0001-...-c000.csv > filename_you_want.csv
avoir un nom de fichier convivial