全部
我们有很多离子格式的文件,在分组和过滤之前,我们必须对数据进行一些计算(它们的总大小约为1.3 TB,每个文件约为200-300 MB)。 我尝试过这两种不同的方式。
首先是并行处理我需要的S3文件列表,并将其转换为RDD [Row]。然后从中创建数据帧。
val rdd = ss.sparkContext.parallelize(suspendedList, suspendedList.size).flatMap(chunk => {
Ranger.consumeStreamToRow(chunk, dfSchema.value)
})
val df = ss.createDataFrame(rdd, schema)
第二种方法是并行化文件列表,并使用AvroParquetWriter将其写入S3中的木地板文件中。将其作为dataFrame重新加载
val rdd = ss.sparkContext.parallelize(suspendedList, suspendedList.size).foreach( chunk => {
Ranger.writeParquetFile(chunk, avroSchema.value, TaskContext.getPartitionId())
})
第二种方法最终要比第一种方法快得多,尽管它必须从S3进行读写。 我注意到的一件事是,与第二种方法相比,第一种方法的垃圾回收时间似乎确实很长,我认为这有助于更长的时间来实际处理数据。
有人可以解释第一种和第二种方法发生了什么吗,为什么即使我正在读写S3,第二种方法的速度也要快得多? (我将执行程序内存设置为10GB,将驱动程序设置为40GB,并且我正在使用Spark EMR)
答案 0 :(得分:0)
根据您的评论,我了解第一种方法读取文件并创建List[Row]
,第二种方法火花用于读取文件。基于这些(如果我错了,请纠正我),我将进行解释。
RDD对象仅具有有关“如何以及在何处读取数据” 的信息,而没有有关实际数据的信息(类似流或python生成器)。因此,要创建RDD,spark将列出s3目录中的所有文件,并(通常)为每个文件创建一个分区,然后将所有内容放入RDD对象中。到目前为止,还没有读取/加载实际数据。
此RDD上的其他transformation(例如过滤器,地图)只会创建更多RDD(以以前的RDD为源)并应执行计算。尚未计算。
仅当执行最后的Action(接收器)(例如save
,collect
,count
)时,才读取实际数据,并且每个分区都由单独的工作程序读取(可能在不同的物理计算机上。
因此,在任何给定时间,一台机器(工人)只需要在内存中保留一小部分数据。
就像在读取文件时一样,先逐行读取文件以首先创建一个List[String]
,文件中的每一行都成为列表中的一个值,而此列表上的map
方法用于将每个String对象转换为Row对象。所有这些都存储在单个驱动程序(主)进程的内存中,几乎没有并行性。因此非常慢,需要大量的内存(和垃圾回收)。
总而言之,请了解代码的哪一部分在驱动程序进程中运行(每个应用程序仅运行一个)以及哪些部分在工作程序中运行(火花应用程序可以在许多不同的计算机上具有多个工作程序进程)。并将您的计算结果移交给工作人员。