Lors de l'utilisation de Scala dans Spark, chaque fois que je vide les résultats en utilisant saveAsTextFile
, il semble que le résultat soit divisé en plusieurs parties. Je ne fais que lui passer un paramètre (chemin).
val year = sc.textFile("apat63_99.txt").map(_.split(",")(1)).flatMap(_.split(",")).map((_,1)).reduceByKey((_+_)).map(_.swap)
year.saveAsTextFile("year")
La raison pour laquelle il est sauvegardé sous forme de plusieurs fichiers est que le calcul est distribué. Si la sortie est suffisamment petite pour que vous pensiez pouvoir la placer sur une seule machine, vous pouvez terminer votre programme avec
val arr = year.collect()
Et puis enregistrez le tableau résultant en tant que fichier. Une autre solution consisterait à utiliser un partitionneur personnalisé, partitionBy
, et à le placer dans une partition, même si cela n’est pas conseillé, car vous n’obtiendrez aucune parallélisation.
Si vous souhaitez que le fichier soit enregistré avec saveAsTextFile
, vous pouvez utiliser coalesce(1,true).saveAsTextFile()
. Cela signifie fondamentalement que le calcul se fond dans 1 partition. Vous pouvez également utiliser repartition(1)
qui n'est qu'un wrapper pour coalesce
avec l'argument shuffle défini sur true. En regardant à travers la source de RDD.scala , j’ai compris comment la plupart de ces choses-là, vous devriez jeter un coup d’œil.
Vous pouvez appeler coalesce(1)
et ensuite saveAsTextFile()
- mais ce pourrait être une mauvaise idée si vous avez beaucoup de données. Comme dans Hadoop, des fichiers distincts sont générés afin de permettre aux mappeurs et aux réducteurs séparés d’écrire dans des fichiers différents. Avoir un seul fichier de sortie n’est une bonne idée que si vous avez très peu de données. Dans ce cas, vous pouvez aussi collect (), comme le dit @aaronman.
Pour ceux qui travaillent avec un plus grand ensemble de données:
rdd.collect()
ne doit pas être utilisé dans ce cas car il collectera toutes les données sous la forme Array
dans le pilote, ce qui constitue le moyen le plus simple de sortir de la mémoire.
rdd.coalesce(1).saveAsTextFile()
ne doit pas non plus être utilisé car le parallélisme des étapes en amont ne sera plus exécuté sur un seul noeud, où les données seront stockées.
rdd.coalesce(1, shuffle = true).saveAsTextFile()
est la meilleure option simple, car elle permet de garder le traitement des tâches en amont parallèle et de n'effectuer que la lecture aléatoire vers un nœud (rdd.repartition(1).saveAsTextFile()
est un synonyme exact).
rdd.saveAsSingleTextFile()
, tel que fourni ci-dessous, permet en outre de stocker le rdd dans un seul fichier avec un nom spécifique tout en conservant les propriétés de parallélisme de rdd.coalesce(1, shuffle = true).saveAsTextFile()
.
Ce qui peut être gênant avec rdd.coalesce(1, shuffle = true).saveAsTextFile("path/to/file.txt")
est qu’il génère en fait un fichier dont le chemin est path/to/file.txt/part-00000
et non path/to/file.txt
.
La solution rdd.saveAsSingleTextFile("path/to/file.txt")
suivante produira en réalité un fichier dont le chemin est path/to/file.txt
:
package com.whatever.package
import org.Apache.spark.rdd.RDD
import org.Apache.hadoop.fs.{FileSystem, FileUtil, Path}
import org.Apache.hadoop.io.compress.CompressionCodec
object SparkHelper {
// This is an implicit class so that saveAsSingleTextFile can be attached to
// SparkContext and be called like this: sc.saveAsSingleTextFile
implicit class RDDExtensions(val rdd: RDD[String]) extends AnyVal {
def saveAsSingleTextFile(path: String): Unit =
saveAsSingleTextFileInternal(path, None)
def saveAsSingleTextFile(path: String, codec: Class[_ <: CompressionCodec]): Unit =
saveAsSingleTextFileInternal(path, Some(codec))
private def saveAsSingleTextFileInternal(
path: String, codec: Option[Class[_ <: CompressionCodec]]
): Unit = {
// The interface with hdfs:
val hdfs = FileSystem.get(rdd.sparkContext.hadoopConfiguration)
// Classic saveAsTextFile in a temporary folder:
hdfs.delete(new Path(s"$path.tmp"), true) // to make sure it's not there already
codec match {
case Some(codec) => rdd.saveAsTextFile(s"$path.tmp", codec)
case None => rdd.saveAsTextFile(s"$path.tmp")
}
// Merge the folder of resulting part-xxxxx into one file:
hdfs.delete(new Path(path), true) // to make sure it's not there already
FileUtil.copyMerge(
hdfs, new Path(s"$path.tmp"),
hdfs, new Path(path),
true, rdd.sparkContext.hadoopConfiguration, null
)
hdfs.delete(new Path(s"$path.tmp"), true)
}
}
}
qui peut être utilisé de cette façon:
import com.whatever.package.SparkHelper.RDDExtensions
rdd.saveAsSingleTextFile("path/to/file.txt")
// Or if the produced file is to be compressed:
import org.Apache.hadoop.io.compress.GzipCodec
rdd.saveAsSingleTextFile("path/to/file.txt.gz", classOf[GzipCodec])
Cet extrait stocke d'abord le RDD avec rdd.saveAsTextFile("path/to/file.txt")
dans un dossier temporaire path/to/file.txt.tmp
comme si nous ne voulions pas stocker les données dans un fichier (ce qui permet de garder le traitement parallèle des tâches en amont).
Et ensuite seulement, en utilisant le système de fichiers hadoop api , nous procédons avec le merge (FileUtil.copyMerge()
) des différents fichiers de sortie pour créer notre fichier unique final path/to/file.txt
.
Comme d'autres l'ont mentionné, vous pouvez collecter ou fusionner votre ensemble de données pour forcer Spark à produire un seul fichier. Mais cela limite également le nombre de tâches Spark pouvant travailler sur votre jeu de données en parallèle. Je préfère laisser créer une centaine de fichiers dans le répertoire de sortie HDFS, puis utiliser hadoop fs -getmerge /hdfs/dir /local/file.txt
pour extraire les résultats dans un seul fichier du système de fichiers local. Cela semble tout à fait logique lorsque votre sortie est un rapport relativement petit, bien sûr.
Dans Spark 1.6.1, le format est celui indiqué ci-dessous. Il crée un fichier de sortie unique. Il est recommandé de l’utiliser si la sortie est suffisamment petite pour être gérée. En gros, elle renvoie un nouveau RDD réduit en partitions numPartitions. par exemple numPartitions = 1, votre calcul peut donc être effectué sur moins de nœuds que vous le souhaitez (par exemple, un nœud dans le cas où numPartitions = 1)
pair_result.coalesce(1).saveAsTextFile("/app/data/")
Vous pourrez le faire dans la prochaine version de Spark. Dans la version 1.0.0 actuelle, il est impossible de le faire manuellement, par exemple, comme vous l'avez mentionné, avec un appel de script bash.
Je tiens également à mentionner que la documentation indique clairement que les utilisateurs doivent faire preuve de prudence lorsqu'ils appellent en réseau avec un nombre de partitions vraiment réduit. cela peut entraîner l'héritage des partitions en amont sur ce nombre de partitions.
Je ne recommanderais pas d'utiliser coalesce (1) à moins que cela ne soit vraiment nécessaire.
Voici ma réponse pour sortir un seul fichier. Je viens d'ajouter coalesce(1)
val year = sc.textFile("apat63_99.txt")
.map(_.split(",")(1))
.flatMap(_.split(","))
.map((_,1))
.reduceByKey((_+_)).map(_.swap)
year.saveAsTextFile("year")
Code:
year.coalesce(1).saveAsTextFile("year")