我有一个火花作业,我在两个数据帧之间进行外连接。 第一个数据帧的大小为260 GB,文件格式为文本文件,分为2200个文件,第二个数据帧的大小为2GB。 然后将大约260 GB的数据帧输出写入S3需要很长时间,因为我已经在EMR上进行了大量更改,因此我取消了2个多小时。
这是我的群集信息。
emr-5.9.0
Master: m3.2xlarge
Core: r4.16xlarge 10 machines (each machine has 64 vCore, 488 GiB memory,EBS Storage:100 GiB)
这是我正在设置的群集配置
capacity-scheduler yarn.scheduler.capacity.resource-calculator :org.apache.hadoop.yarn.util.resource.DominantResourceCalculator
emrfs-site fs.s3.maxConnections: 200
spark maximizeResourceAllocation: true
spark-defaults spark.dynamicAllocation.enabled: true
我尝试手动设置内存组件,如下所示,性能更好,但同样需要花费很长时间
- num-executors 60 - conf spark.yarn.executor.memoryOverhead = 9216 --executor-memory 72G --conf spark.yarn.driver.memoryOverhead = 3072 --driver-memory 26G --execeror-cores 10 --driver-cores 3 --conf spark.default.parallelism = 1200
我没有使用默认分区将数据保存到S3中。
添加有关作业和查询计划的所有详细信息,以便易于理解。
真正的原因是分区。这大部分时间都在占用。 因为我有2K文件,所以如果我使用re分区像200输出 文件以十万分之一的形式出现,然后在火花中再次加载并不是一件好事 故事。
在下面图片GC对我来说太高了..请问我们必须处理这个问题吗?
以下是节点健康状态。此时数据已保存到S3中,难怪为什么我只能看到两个节点处于活动状态并且都处于空闲状态。
这是加载时的群集详细信息。此时我可以看到群集已被充分利用,但在将数据保存到S3时,许多节点都是免费的。
最后这是我的代码,我执行Join然后保存到S3 ...
import org.apache.spark.sql.expressions._
val windowSpec = Window.partitionBy("uniqueFundamentalSet", "PeriodId", "SourceId", "StatementTypeCode", "StatementCurrencyId", "FinancialStatementLineItem_lineItemId").orderBy(unix_timestamp($"TimeStamp", "yyyy-MM-dd HH:mm:ss.SSS").cast("timestamp").desc)
val latestForEachKey = df2resultTimestamp.withColumn("rank", row_number.over(windowSpec)).filter($"rank" === 1).drop("rank", "TimeStamp")
val columnMap = latestForEachKey.columns.filter(c => c.endsWith("_1") & c != "FFAction|!|_1").map(c => c -> c.dropRight(2)) :+ ("FFAction|!|_1", "FFAction|!|")
val exprs = columnMap.map(t => coalesce(col(s"${t._1}"), col(s"${t._2}")).as(s"${t._2}"))
val exprsExtended = Array(col("uniqueFundamentalSet"), col("PeriodId"), col("SourceId"), col("StatementTypeCode"), col("StatementCurrencyId"), col("FinancialStatementLineItem_lineItemId")) ++ exprs
//Joining both dara frame here
val dfMainOutput = (dataMain.join(latestForEachKey, Seq("uniqueFundamentalSet", "PeriodId", "SourceId", "StatementTypeCode", "StatementCurrencyId", "FinancialStatementLineItem_lineItemId"), "outer") select (exprsExtended: _*)).filter(!$"FFAction|!|".contains("D|!|"))
//Joing ends here
val dfMainOutputFinal = dfMainOutput.na.fill("").select($"DataPartition", $"PartitionYear", $"PartitionStatement", concat_ws("|^|", dfMainOutput.schema.fieldNames.filter(_ != "DataPartition").filter(_ != "PartitionYear").filter(_ != "PartitionStatement").map(c => col(c)): _*).as("concatenated"))
val headerColumn = dataHeader.columns.toSeq
val headerFinal = headerColumn.mkString("", "|^|", "|!|").dropRight(3)
val dfMainOutputFinalWithoutNull = dfMainOutputFinal.withColumn("concatenated", regexp_replace(col("concatenated"), "|^|null", "")).withColumnRenamed("concatenated", headerFinal)
// dfMainOutputFinalWithoutNull.repartition($"DataPartition", $"PartitionYear", $"PartitionStatement")
.write
.partitionBy("DataPartition", "PartitionYear", "PartitionStatement")
.format("csv")
.option("timestampFormat", "yyyy/MM/dd HH:mm:ss ZZ")
.option("nullValue", "")
.option("delimiter", "\t")
.option("quote", "\u0000")
.option("header", "true")
.option("codec", "bzip2")
.save(outputFileURL)
答案 0 :(得分:3)
您正在运行五个c3.4large EC2实例,每个实例具有30GB的RAM。所以总共只有150GB,远远小于你要加入的> 200GB数据帧。因此大量的磁盘溢出。也许您可以启动r类型EC2实例(内存优化而不是c类型,这是计算优化的),并查看是否有性能改进。
答案 1 :(得分:2)
S3是一个对象存储而不是文件系统,因此最终的一致性,非原子重命名操作引起的问题,即每次执行程序写入作业的结果时,每个都写入外部的临时目录必须写入文件的主目录(在S3上),一旦完成所有执行程序,就完成重命名以获得原子排他性。这在hdfs这样的标准文件系统中都很好,其中重命名是即时的,但在像S3这样的对象存储上,这不利于S3上的重命名以6MB / s的速度完成。
要解决上述问题,请确保设置以下两个配置参数
1)spark.hadoop.mapreduce.fileoutputcommitter.algorithm.version = 2
对于此参数的默认值,即1,commitTask将任务生成的数据从任务临时目录移动到作业临时目录,当所有任务完成时,commitJob将数据从作业临时目录移动到最终目标。因为驱动程序正在执行commitJob的工作,对于S3,此操作可能需要很长时间。用户可能经常认为他/她的小区是“悬挂”的。但是,当mapreduce.fileoutputcommitter.algorithm.version的值为2时,commitTask会将任务生成的数据直接移动到最终目标,commitJob基本上是无操作。
2)spark.speculation = false
如果此参数设置为true,那么如果一个或多个任务在某个阶段中运行缓慢,则会重新启动它们。如上所述,通过spark作业对S3的写入操作非常慢,因此随着输出数据大小的增加,我们可以看到许多任务重新启动。
这与最终的一致性(将文件从临时目录移动到主数据目录时)可能导致FileOutputCommitter进入死锁状态,因此作业可能会失败。
<强>替代地强>
您可以先将输出写入EMR上的本地HDFS,然后使用hadoop distcp命令将数据移至S3。这大大提高了整体输出速度。但是,您的EMR节点上需要足够的EBS存储空间,以确保所有输出数据都适合。
此外,您可以以ORC格式编写输出数据,这将大大压缩输出大小。
参考: