Для тих, хто працює з більшим набором даних :
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 {
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 = {
val hdfs = FileSystem.get(rdd.sparkContext.hadoopConfiguration)
hdfs.delete(new Path(s"$path.tmp"), true)
codec match {
case Some(codec) => rdd.saveAsTextFile(s"$path.tmp", codec)
case None => rdd.saveAsTextFile(s"$path.tmp")
}
hdfs.delete(new Path(path), true)
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)
}
}
}
які можна використовувати таким чином:
import com.whatever.package.SparkHelper.RDDExtensions
rdd.saveAsSingleTextFile("path/to/file.txt")
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
.