我的应用程序可以在微小对象的大型数据集上很好地工作。但是随着对象的增长,对数据帧的操作会遇到内存不足异常。基本上,应用程序读取序列文件,然后将键/值转换为具有某些列的行对象,进行一些排序,最后转换回rdd以编写序列文件。
如果行对象很小-大约20 KB的数据-一切都很好。在第二次运行中,行对象包含大约2mb的数据,并且spark遇到内存不足的问题。我测试了几个选项,更改了分区的大小和数量,但是应用程序运行不稳定。
为重现此问题,我创建了以下示例代码。将rd个10000个int对象映射到长度为2mb的字符串(概率为4mb,假设每个字符16位)。 10.000乘2mb约为20gb的数据。 show()排序操作和show()操作再次按预期工作。日志表明spark正在存储到磁盘。 但是,当将结果写入csv文件,hadoop文件或仅调用JavaRDD()时,应用程序以内存不足结尾:Java堆空间 似乎spark仅在内存中保存数据。
// create spark session
SparkSession spark = SparkSession.builder()
.appName("example1")
.master("local[2]")
.config("spark.driver.maxResultSize","0")
.getOrCreate();
JavaSparkContext sc = JavaSparkContext.fromSparkContext(spark.sparkContext());
// base to generate huge data
List<Integer> list = new ArrayList<>();
for (int val = 1; val < 10000; val++) {
int valueOf = Integer.valueOf(val);
list.add(valueOf);
}
// create simple rdd of int
JavaRDD<Integer> rdd = sc.parallelize(list,200);
// use map to create large object per row
JavaRDD<Row> rowRDD =
rdd.map(value -> RowFactory.create(String.valueOf(value), createLongText(UUID.randomUUID().toString(), 2 * 1024 * 1024)))
;
StructType type = new StructType();
type = type
.add("c1", DataTypes.StringType)
.add( "c2", DataTypes.StringType );
Dataset<Row> df = spark.createDataFrame(rowRDD, type);
// works
df.show();
df = df.sort( col("c2").asc() );
// takes a lot of time but works
df.show();
// OutOfMemoryError: java heap space
df.write().csv("d:/temp/my.csv");
// OutOfMemoryError: java heap space
df
.toJavaRDD()
.mapToPair(row -> new Tuple2(new Text(row.getString(0)), new Text( row.getString(1))))
.saveAsHadoopFile("d:\\temp\\foo", Text.class, Text.class, SequenceFileOutputFormat.class );
操作createLongText()创建一个长字符串。
private static String createLongText( String text, int minLength ) {
String longText = text;
while( longText.length() < minLength ) {
longText = longText + longText;
}
return longText;
}
JVM在Java 1.8u171中具有4GB的堆(-Xms4g -Xmx4g),具有默认GC(GC1),并且在本地模式下与-XX:+ UseParallelGC可选运行。堆大小朝着上限增加,并且gc活性增加。在大多数情况下,oom使驱动程序崩溃,在某些情况下,高gc活动导致驱动程序超时。 这是spark.driver.maxResultSize首次设置为0(无限制)。当使用较小的对象(几乎相同数量的数据,具有较少数据的更多对象)时,任何对象都可以正常工作。 似乎从dataFrame到rdd的任何转换都会绑定许多内存资源,并强制将所有结果发送回驱动程序。我在崩溃之前创建了一个堆转储,并看到了一个大型的scala数组数组。内部数组包含约32mb(分区大小!?),内部数组的总和约为分区数(115)
8/07/26 21:58:31 INFO MemoryStore: Block taskresult_144 stored as bytes in memory (estimated size 47.7 MB, free 86.3 MB)
18/07/26 21:58:31 INFO BlockManagerInfo: Added taskresult_144 in memory on blackhawk:61185 (size: 47.7 MB, free: 86.4 MB)
18/07/26 21:58:31 INFO Executor: Finished task 143.0 in stage 1.0 (TID 144). 50033768 bytes result sent via BlockManager)
18/07/26 21:58:31 INFO TaskSetManager: Starting task 145.0 in stage 1.0 (TID 146, localhost, executor driver, partition 145, PROCESS_LOCAL, 8355 bytes)
18/07/26 21:58:31 INFO Executor: Running task 145.0 in stage 1.0 (TID 146)
18/07/26 21:58:35 INFO TaskSetManager: Finished task 104.0 in stage 1.0 (TID 105) in 36333 ms on localhost (executor driver) (105/200)
18/07/26 21:58:35 ERROR Executor: Exception in task 144.0 in stage 1.0 (TID 145)
java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3236)
at java.lang.StringCoding.safeTrim(StringCoding.java:79)
at java.lang.StringCoding.encode(StringCoding.java:365)
at java.lang.String.getBytes(String.java:941)
at org.apache.spark.unsafe.types.UTF8String.fromString(UTF8String.java:141)
at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.StaticInvoke_1$(Unknown Source)
at org.apache.spark.sql.catalyst.expressions.GeneratedClass$SpecificUnsafeProjection.apply(Unknown Source)
at org.apache.spark.sql.catalyst.encoders.ExpressionEncoder.toRow(ExpressionEncoder.scala:288)
at org.apache.spark.sql.SparkSession$$anonfun$4.apply(SparkSession.scala:592)
at org.apache.spark.sql.SparkSession$$anonfun$4.apply(SparkSession.scala:592)
有什么想法吗?还是Spark 2.3.1中的错误?
答案 0 :(得分:0)
尝试不使用GC1垃圾回收程序,这可能是连续分配内存和GC1 32mb区域的错误。
请阅读http://labs.criteo.com/2018/01/spark-out-of-memory/,可能是这样吗?