**
SparkSql将数据写入SFTP时内存溢出
**
最近接到个需求,大概的意思是将集群中的数据进行ETL后保存至SFTP。
之前进行类似的需求主要思路如下:
spark处理完数据后,将数据生成文件保存hdfs上,然后使用一个程序读取这个文件,直接以流的形式或者将数据读入内存均可,抱着快速完成需求的初衷,使用了一个第三方jar包,我觉得开源的依赖使用起来更加简洁易维护。具体的使用方式如下:
DataFrame.format("com.springml.spark.sftp").
option("host", config.getString("sftp.host")).
option("username", config.getString("sftp.user")).
option("password", config.getString("sftp.password")).
option("fileType", "json").
option("hdfsTempLocation", config.getString("hdfsTempLocation")).
save(config.getString("sftp.path")+taskId+".json")
DataFrame在进行数据输出的时候指定使用com.springml.spark.sftp这个依赖生成json文件。
但是程序运行的时候报了错,是一个非常常见又让人摸不着头脑的错,就是名声在外的OOM。
在排除资源以及Spark任务运行方式的影响后,我把错误的源头锁定到了这个依赖上面。己经排查,发现问题就出现在下面的代码上:
if (fileType.equals("json")) {
df.coalesce(1).write.json(hdfsTempLocation)
} else if (fileType.equals("txt")) {
df.coalesce(1).write.text(hdfsTempLocation)
} else if (fileType.equals("xml")) {
df.coalesce(1).write.format(constants.xmlClass)
.option(constants.xmlRowTag, rowTag)
.option(constants.xmlRootTag, rootTag).save(hdfsTempLocation)
}
else if (fileType.equals("parquet")) {
df.coalesce(1).write.parquet(hdfsTempLocation)
return copiedParquetFile(hdfsTempLocation)
} else if (fileType.equals("csv")) {
df.coalesce(1).
write.
option("header", header).
option("delimiter", delimiter).
optionNoNull("codec", Option(codec)).
csv(hdfsTempLocation)
} else if (fileType.equals("avro")) {
df.coalesce(1).write.format("com.databricks.spark.avro").save(hdfsTempLocation)
}
发现没有,无论以何种文件格式输出,DataFrame都会进行一次coalesce以将分区缩小至一个,它这么做的原因应该是为了保证最终输出的文件数据是1。熟悉spark的同志们都知道,coalesce是不经过shuffle的,这就是导致OOM的原因。于是我调整代码,在DF输出之前repartition,将所有数据shuffle至一个分区。问题得到解决。个人感觉这并不是最好的解决方法,如果遇到大体量数据,repartition也会有问题。如果大家有更好的方式,欢迎指教。