it-swarm.com.de

wie lässt sich saveAsTextFile NICHT in mehrere Dateien aufteilen?

Bei der Verwendung von Scala in Spark scheint die Ausgabe in mehrere Teile aufzuteilen, wenn ich die Ergebnisse mit saveAsTextFile ausspeichere. Ich übergebe nur einen Parameter (Pfad). 

val year = sc.textFile("apat63_99.txt").map(_.split(",")(1)).flatMap(_.split(",")).map((_,1)).reduceByKey((_+_)).map(_.swap)
year.saveAsTextFile("year")
  1. Entspricht die Anzahl der Ausgänge der Anzahl der verwendeten Reduzierungen? 
  2. Bedeutet das, dass die Ausgabe komprimiert ist? 
  3. Ich weiß, dass ich die Ausgabe mithilfe von Bash kombinieren kann. Gibt es eine Option, um die Ausgabe in einer einzelnen Textdatei zu speichern, ohne sie zu teilen? Ich habe mir die API-Dokumente angesehen, aber es sagt nicht viel darüber aus.
70
user2773013

Der Grund, warum es als mehrere Dateien gespeichert wird, liegt darin, dass die Berechnung verteilt wird. Wenn die Ausgabe so klein ist, dass Sie denken, dass Sie sie auf eine Maschine passen, können Sie Ihr Programm mit beenden 

val arr = year.collect()

Speichern Sie dann das resultierende Array als Datei. Eine andere Möglichkeit wäre die Verwendung eines benutzerdefinierten Partitionierers partitionBy und die Einstellung, dass alles zu einer Partition gehört.

Wenn Sie die Datei mit saveAsTextFile speichern möchten, können Sie coalesce(1,true).saveAsTextFile() verwenden. Dies bedeutet im Wesentlichen, dass die Berechnung dann zu einer Partition zusammenwächst. Sie können auch repartition(1) verwenden, das nur ein Wrapper für coalesce ist, wobei das Shuffle-Argument auf true gesetzt ist. Wenn ich durch die Quelle von RDD.scala schaue, habe ich herausgefunden, dass das meiste aus diesem Zeug herauskommt. Sie sollten einen Blick darauf werfen.

94
aaronman

Für diejenigen, die mit einem größeren Datensatz arbeiten:

  • rdd.collect() sollte in diesem Fall nicht verwendet werden, da collect alle Daten als Array im Treiber gespeichert werden. Dies ist der einfachste Weg, um nicht mehr genügend Arbeitsspeicher zur Verfügung zu haben.

  • rdd.coalesce(1).saveAsTextFile() sollte auch nicht verwendet werden, da die Parallelität von Upstream-Stufen verloren geht, um auf einem einzelnen Knoten ausgeführt zu werden, von dem aus Daten gespeichert werden.

  • rdd.coalesce(1, shuffle = true).saveAsTextFile() ist die einfachste Option , da die Verarbeitung von Upstream-Aufgaben parallel bleibt und dann nur die Zufallswiedergabe an einen Knoten ausgeführt wird (rdd.repartition(1).saveAsTextFile() ist ein genaues Synonym). .

  • rdd.saveAsSingleTextFile(), wie unten angegeben, ermöglicht es zusätzlich, die rdd in einer einzelnen Datei mit einem bestimmten Namen zu speichern, während die Parallelitätseigenschaften von rdd.coalesce(1, shuffle = true).saveAsTextFile() beibehalten werden.


Etwas, das mit rdd.coalesce(1, shuffle = true).saveAsTextFile("path/to/file.txt") unpraktisch sein kann, ist, dass es tatsächlich eine Datei erzeugt, deren Pfad path/to/file.txt/part-00000 Und nicht path/to/file.txt Ist.

Die folgende Lösung rdd.saveAsSingleTextFile("path/to/file.txt") erstellt tatsächlich eine Datei mit dem Pfad 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
      )
      // Working with Hadoop 3?: https://stackoverflow.com/a/50545815/9297144

      hdfs.delete(new Path(s"$path.tmp"), true)
    }
  }
}

was kann auf diese Weise verwendet werden:

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])

Dieser Ausschnitt:

  • Speichern Sie zuerst die Rdd mit rdd.saveAsTextFile("path/to/file.txt") in einem temporären Ordner path/to/file.txt.tmp, Als wollten wir keine Daten in einer Datei speichern (wodurch die Verarbeitung von Upstream-Aufgaben parallel bleibt).

  • Und dann fahren wir nur mit hadoop file system api mit merge (FileUtil.copyMerge()) der verschiedenen Ausgabedateien fort, um unsere endgültige Ausgabedatei zu erstellen path/to/file.txt.

23
Xavier Guihot

Sie könnten coalesce(1) und dann saveAsTextFile() aufrufen - aber es könnte eine schlechte Idee sein, wenn Sie viele Daten haben. Separate Dateien pro Split werden genau wie in Hadoop generiert, damit separate Mapper und Reduktionen in unterschiedliche Dateien schreiben können. Eine einzige Ausgabedatei ist nur eine gute Idee, wenn Sie sehr wenige Daten haben. In diesem Fall können Sie auch collect () verwenden, wie @aaronman sagte.

18
marekinfo

Wie bereits erwähnt, können Sie Ihre Daten sammeln oder zusammenführen, um zu erzwingen, dass Spark eine einzelne Datei erstellt. Dies begrenzt jedoch auch die Anzahl der Spark-Aufgaben, die parallel an Ihrem Datensatz arbeiten können. Ich ziehe es vor, Hundert Dateien im Ausgabe-HDFS-Verzeichnis erstellen zu lassen und dann mit hadoop fs -getmerge /hdfs/dir /local/file.txt die Ergebnisse in eine einzige Datei im lokalen Dateisystem zu extrahieren. Dies ist am sinnvollsten, wenn es sich bei Ihrer Ausgabe natürlich um einen relativ kleinen Bericht handelt.

4
Matt

Sie können repartition() aufrufen und folgendermaßen vorgehen:

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

In Spark 1.6.1 sieht das Format wie folgt aus. Es wird eine einzige Ausgabedatei erstellt. Es ist am besten, sie zu verwenden, wenn die Ausgabe klein genug ist, um damit umzugehen. Grundsätzlich wird eine neue RDD zurückgegeben, die in numPartitions-Partitionen reduziert ist. z.B auf numPartitions = 1 kann dies dazu führen, dass Ihre Berechnung auf weniger Knoten erfolgt, als Sie möchten (z. B. ein Knoten im Fall von numPartitions = 1).

pair_result.coalesce(1).saveAsTextFile("/app/data/")

Sie können dies in der nächsten Version von Spark tun. In der aktuellen Version 1.0.0 ist dies nicht möglich, es sei denn, Sie tun es manuell, beispielsweise wie Sie es erwähnt haben, mit einem Bash-Skriptaufruf. 

1
gprivitera

Ich möchte auch erwähnen, dass die Dokumentation eindeutig besagt, dass die Benutzer beim Aufruf von coalesce mit einer wirklich geringen Anzahl von Partitionen vorsichtig sein sollten. Dies kann dazu führen, dass Upstream-Partitionen diese Anzahl von Partitionen erben.

Ich würde die Verwendung von coalesce (1) nur empfehlen, wenn dies wirklich erforderlich ist. 

1
Franck Tago

Hier ist meine Antwort, um eine einzelne Datei auszugeben. Ich habe gerade coalesce(1) hinzugefügt.

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