我有一组日志文件,我想读入RDD。 这些日志文件都是压缩的gzip文件,文件名是 加盖日期。
我一直在使用sc.wholeTextFiles()
来读取文件,看起来我一直在遇到Java堆内存问题。为了解决这个问题,我决定在一台机器上针对单个文件运行它作为测试用例。
我从这里获得了文件:
http://dumps.wikimedia.org/other/pagecounts-raw/
以下是文件的大小,包括压缩版本和未压缩版本:
myuser@fembuntu$ ls -ltr pagecounts-20090505-180000*
-rw-rw-r-- 1 myuser myuser 65170192 Sep 20 2009 pagecounts-20090505-180000.gz
-rw-rw-r-- 1 myuser myuser 233007266 Jan 22 00:23 pagecounts-20090505-180000.txt
并且机器上的可用内存如下:
myuser@fembuntu:~$ free -tm
total used free shared buffers cached
Mem: 4856 3018 1838 123 27 407
-/+ buffers/cache: 2583 2273
Swap: 5080 849 4231
Total: 9937 3867 6069
所以我启动了spark-shell,给执行者2G的内存:
$ spark-shell --executor-memory 2G
scala> val pc_loc = "file:///home/myuser/data/pagecounts"
scala> val filename="/pagecounts-20090505-180000.gz"
filename: String = /pagecounts-20090505-180000.gz
在这里,我通过sc.textFile()
读取数据并显示前2行:
scala> var rdd=sc.textFile(pc_loc + filename)
rdd: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[1] at textFile at <console>:31
scala> rdd.take(2)
res0: Array[String] = Array(aa.b Help:Books 1 5263, aa.b Main_Page 1 5416)
工作正常。
这里我使用sc.wholeTextFiles()
,并通过flatMapValues()
拆分新行以获得一对RDD,其中行是键值对。这些值对应于使用sc.textFile()
获得的RDD中的行。关键是文件路径。
scala> val pair_rdd=sc.wholeTextFiles(pc_loc + filename).flatMapValues(y => y.split("\n"))
pair_rdd: org.apache.spark.rdd.RDD[(String, String)] = MapPartitionsRDD[4] at flatMapValues at <console>:31
但是当我执行一个动作时出现堆错误:
scala> pair_rdd.take(2)
16/01/22 01:13:36 ERROR Executor: Exception in task 0.0 in stage 1.0 (TID 1)
java.lang.OutOfMemoryError: Java heap space
at java.nio.HeapCharBuffer.<init>(HeapCharBuffer.java:57)
at java.nio.CharBuffer.allocate(CharBuffer.java:335)
at java.nio.charset.CharsetDecoder.decode(CharsetDecoder.java:795)
at org.apache.hadoop.io.Text.decode(Text.java:412)
任何人都可以解释这里发生了什么吗?为什么flatMapValues
调用拆分线似乎会破坏Java堆内存使用量,从而导致堆错误?
答案 0 :(得分:8)
您遇到的问题并非真正针对具有textFile
方案的wholeTextFiles
vs flatMapValues
。看起来您的程序甚至没有达到数据平坦的程度,而且我很确定当您拨打count
而不是mapValues
时,您会收到同样的异常。
实际上只需要创建大型对象。请记住,wholeTextFiles
必须立即读取文件的完整内容,不能将其部分溢出到磁盘或部分垃圾回收。虽然200MB左右不是特别令人印象深刻,但是单个对象处理的却相当多。此外,它必须驻留在一台机器上,这意味着分配负载更加困难。
与wholeTextFiles
不同,textFile
在此特定情况下提供更高的粒度。单个对象必须处理的数据少得多,如果不再需要,可以有效地进行垃圾回收。
忽略对象的大小,看起来你在本地模式下使用Spark。这意味着一切都由一个JVM处理。由于堆由所有线程共享,这意味着可用于实际处理的内存量可能低于您的预期。
最后你应该记住,只有一部分可用内存是为堆保留的。请参阅Garbage Collector Ergonomics和How is the default java heap size determined?。如果必须处理大型对象,则始终可以使用-Xms
/ -Xmx
Java选项覆盖默认的初始和最大堆大小。
答案 1 :(得分:2)
在第一种情况下,当你调用动作时,Spark会发现它需要返回第二行。由于RDD被懒惰地评估,它只读取它需要返回的数据。
在第二种情况下,当您调用操作时,将调用两个操作,读取文件并对整个文件应用flatMapValues
。因此,在这种情况下,需要读取和处理整个文件,然后返回第二行。
至少可以解释为什么在第二种情况下需要更多内存。