як змусити saveAsTextFile НЕ розділити вихід на кілька файлів?


79

Коли я використовую Scala у Spark, щоразу, коли я викидаю результати, використовуючи saveAsTextFile, здається, це ділить вихід на кілька частин. Я просто передаю йому параметр (шлях).

val year = sc.textFile("apat63_99.txt").map(_.split(",")(1)).flatMap(_.split(",")).map((_,1)).reduceByKey((_+_)).map(_.swap)
year.saveAsTextFile("year")
  1. Чи відповідає кількість виходів кількості використаних редукторів?
  2. Чи означає це, що результат виходу стискається?
  3. Я знаю, що можу комбінувати вихідні дані разом за допомогою bash, але чи є можливість зберігати вихідні дані в одному текстовому файлі, не розділяючи ?? Я подивився документи на API, але це мало що говорить про це.

2
Як правило, поганою практикою є використання лише одного файлу в Big Data, якщо цей файл великий.
самий найкращий

Яка найкраща практика тоді, якщо результатом було, скажімо, відсортований файл? Зберігати його як колекцію файлів і зробити так, щоб безліч імен вихідних файлів були якимось індексом (тобто щось на зразок першого файлу називалося "aa", середнє - як "fg", останнє - "zzy")?
Рдесмонд

Часто буває, що важка іскрова робота генерує лише дуже малий вихід (агрегація, kpis, популярність, ...), який виробляється на hdfs, але, швидше за все, він буде використаний додатками, не пов’язаними з великими даними. У цьому випадку чистіше і простіше мати добре названий єдиний файл для передачі та споживання.
Xavier Guihot

Відповіді:


101

Причиною збереження його як кількох файлів є обчислення, яке розподіляється. Якщо вихід достатньо малий, щоб ви думали, що можете помістити його на одній машині, тоді ви можете закінчити програму

val arr = year.collect()

А потім збережіть отриманий масив у вигляді файлу. Іншим способом було б використовувати власний розділ, partitionByі зробити так, щоб все переходило до одного розділу, хоча це не доцільно, оскільки ви не отримаєте ніякого розпаралелювання.

Якщо вам потрібно зберегти файл, saveAsTextFileви можете використовувати coalesce(1,true).saveAsTextFile(). В основному це означає, що обчислення потім об’єднуються в 1 розділ. Ви також можете використовувати, repartition(1)що є просто обгорткою, для coalesceаргументу перетасовки встановленого в true. Переглядаючи джерело RDD.scala - це те, як я зрозумів, більшість цих матеріалів, вам слід поглянути.


2
як зберегти масив як текстовий файл ?? для масиву немає функції saveAsTextFile. просто для RDD.
user2773013

5
@ user2773013 добре, що підхід для цього був би coalesceабо partitionпідхід, який я запропонував, але насправді немає сенсу зберігати на hdfs, якщо це лише на 1 вузлі, тому використання
колекції

Дуже корисна відповідь .... Не бачив розділу За допомогою або злиття в підручниках, які я читав ...

36

Для тих, хто працює з більшим набором даних :

  • rdd.collect()не слід використовувати в цьому випадку, оскільки він буде збирати всі дані як Arrayу драйвері, що є найпростішим способом вийти з пам'яті.

  • rdd.coalesce(1).saveAsTextFile() також не слід використовувати, оскільки паралельність вихідних етапів буде втрачена для виконання на одному вузлі, звідки будуть зберігатися дані.

  • rdd.coalesce(1, shuffle = true).saveAsTextFile() є найкращим простим варіантом, оскільки він буде тримати паралельну обробку вихідних завдань, а потім виконувати перетасовку лише до одного вузла ( rdd.repartition(1).saveAsTextFile()є точним синонімом).

  • rdd.saveAsSingleTextFile()як зазначено нижче, додатково дозволяє зберігати rdd в одному файлі з певним ім'ям , зберігаючи паралельність властивостей rdd.coalesce(1, shuffle = true).saveAsTextFile().


Щось, що може бути незручним, rdd.coalesce(1, shuffle = true).saveAsTextFile("path/to/file.txt")це те, що насправді створюється файл, шлях якого є, path/to/file.txt/part-00000а ні path/to/file.txt.

Наступне рішення rdd.saveAsSingleTextFile("path/to/file.txt")насправді створить файл, шлях якого 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)
    }
  }
}

які можна використовувати таким чином:

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

Цей фрагмент:

  • Спочатку зберігає rdd з rdd.saveAsTextFile("path/to/file.txt")у тимчасовій папці path/to/file.txt.tmpтак, ніби ми не хочемо зберігати дані в одному файлі (що паралельно обробляє попередні завдання)

  • І тоді, лише використовуючи api файлової системи hadoop , ми продовжуємо злиття ( FileUtil.copyMerge()) різних вихідних файлів, щоб створити наш остаточний вихідний єдиний файл path/to/file.txt.


22

Ви можете зателефонувати, coalesce(1)а потім saveAsTextFile()- але це може бути поганою ідеєю, якщо у вас багато даних. Окремі файли за спліт генеруються так само, як у Hadoop, щоб дозволити окремим картографам і редукторам писати в різні файли. Наявність одного вихідного файлу - це лише гарна ідея, якщо у вас дуже мало даних, і в цьому випадку ви можете також збирати (), як сказав @aaronman.


Ніцца не думав coalesceчистіше, ніж collect
базікати з секціонером

1
це працює. Але якщо ви використовуєте злиття, це означає, що ви використовуєте лише 1 редуктор. Хіба це не сповільнить процес, оскільки використовується лише 1 редуктор ??
user2773013

1
Так, але це те, про що ви просите. Spark видає по одному файлу на розділ. З іншого боку, чому ви дбаєте про кількість файлів? Під час читання файлів у spark ви можете просто вказати батьківський каталог, і всі розділи читаються як єдиний RDD
Девід

1
Не coalesce(1)будь ласка, якщо ви не знаєте, що робите .
gsamaras

4

Як вже згадували інші, ви можете зібрати або об’єднати свій набір даних, щоб змусити Spark створити один файл. Але це також обмежує кількість завдань Spark, які можуть паралельно працювати з вашим набором даних. Я вважаю за краще дозволити йому створити сотню файлів у вихідному каталозі HDFS, а потім використовувати hadoop fs -getmerge /hdfs/dir /local/file.txtдля вилучення результатів в один файл у локальній файловій системі. Звичайно, це має найбільший сенс, коли ваш результат - порівняно невеликий звіт.


2

Ви можете зателефонувати repartition()і слідувати таким чином:

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

введіть тут опис зображення


1

Ви зможете це зробити в наступній версії Spark, у поточній версії 1.0.0 це неможливо, якщо ви не зробите це вручну якось, наприклад, як ви вже згадували, за допомогою виклику сценарію bash.


2
наступна версія Spark вже тут, і незрозуміло, як це зробити :(
Ciprian Tomoiagă

1

Я також хочу зазначити, що в документації чітко зазначено, що користувачі повинні бути обережними при виклику злиття з дійсно невеликою кількістю розділів. це може призвести до того, що розділи вище за течією успадкують цю кількість розділів.

Я не рекомендував би використовувати коалесценцію (1), якщо це дійсно не потрібно.


1

У Spark 1.6.1 формат такий, як показано нижче. Він створює єдиний вихідний файл. Краще використовувати його, якщо висновок достатньо малий для обробки. В основному він повертає новий RDD, який зводиться до розділів numPartitions. Якщо ви робите різке злиття, наприклад, до numPartitions = 1, це може призвести до того, що ваші обчислення відбуватимуться на меншій кількості вузлів, ніж вам подобається (наприклад, один вузол у випадку numPartitions = 1)

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

0

Ось моя відповідь на вихід одного файлу. Я щойно додавcoalesce(1)

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

Код:

year.coalesce(1).saveAsTextFile("year")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.