如何在EMR上调整spark作业以在S3

时间:2017-10-15 11:16:47

标签: apache-spark-sql spark-dataframe hadoop2 amazon-emr

我有一个火花作业,我在两个数据帧之间进行外连接。 第一个数据帧的大小为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输出   文件以十万分之一的形式出现,然后在火花中再次加载并不是一件好事   故事。

在下面的图片中我不知道为什么在项目之后再次调用sort enter image description here

在下面图片GC对我来说太高了..请问我们必须处理这个问题吗? Executor and GC details

以下是节点健康状态。此时数据已保存到S3中,难怪为什么我只能看到两个节点处于活动状态并且都处于空闲状态。 This is my nodes details .At this point Data is getting saved into S3

这是加载时的群集详细信息。此时我可以看到群集已被充分利用,但在将数据保存到S3时,许多节点都是免费的。 Fully Utilized clsuter

最后这是我的代码,我执行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)

2 个答案:

答案 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格式编写输出数据,这将大大压缩输出大小。

参考:

https://medium.com/@subhojit20_27731/apache-spark-and-amazon-s3-gotchas-and-best-practices-a767242f3d98