web-dev-qa-db-fra.com

comment faire pour saveAsTextFile PAS diviser la sortie en plusieurs fichiers?

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")
  1. Le nombre de sorties correspond-il au nombre de réducteurs utilisés? 
  2. Est-ce que cela signifie que la sortie est compressée? 
  3. Je sais que je peux combiner la sortie à l’aide de bash, mais existe-t-il une option permettant de stocker la sortie dans un seul fichier texte, sans fractionner ?? J'ai consulté la documentation de l'API, mais cela n'en dit pas beaucoup.
70
user2773013

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.

94
aaronman

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.

18
marekinfo

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.

13
Xavier Guihot

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.

4
Matt

Vous pouvez appeler repartition() et suivre cette voie:

val year = sc.textFile("apat63_99.txt").map(_.split(",")(1)).flatMap(_.split(",")).map((_,1)).reduceByKey((_+_)).map(_.swap)

var repartitioned = year.repartition(1)
repartitioned.saveAsTextFile("C:/Users/TheBhaskarDas/Desktop/wc_spark00")

 enter image description here

2
Bhaskar Das

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/")
1

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. 

1
gprivitera

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. 

1
Franck Tago

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")
0
Ian Mendoza