了解如何在并行转换多个数据帧时实现最佳并行度
我有一组路径
val paths = Array("path1", "path2", .....
我正在从每个路径加载数据帧,然后转换并写入目标路径
paths.foreach(path => {
val df = spark.read.parquet(path)
df.transform(processData).write.parquet(path+"_processed")
})
转换processData
与我正在加载的数据帧无关。
这限制了一次只能处理一个数据帧,并且我的大多数群集资源都处于空闲状态。由于每个数据帧的处理都是独立的,因此我将Array
转换为scala的ParArray
。
paths.par.foreach(path => {
val df = spark.read.parquet(path)
df.transform(processData).write.parquet(path+"_processed")
})
现在它正在集群中使用更多资源。我仍在尝试了解其工作原理以及如何在此处微调并行处理
如果我将使用ForkJoinPool
的默认scala并行度提高到更高的数量,是否会导致在驱动程序端产生更多线程,并且将处于锁定状态,等待foreach
函数完成并最终杀死了驾驶员?
它如何影响诸如EventLoggingListnener
之类的集中式火花,当并行处理多个数据帧时,它们需要处理更多的事件流入。
我应该考虑使用哪些参数来优化资源利用。
任何其他方法
我可以理解的任何资源都将非常有用
答案 0 :(得分:0)
之所以这么慢,是因为spark非常擅长并行处理存储在一个大数据框中的大量数据的计算。但是,处理大量数据帧非常不好。它将使用所有执行程序在一个执行程序上开始计算(即使并不需要全部执行程序),并等待其完成后再开始下一个执行程序。这导致许多不活动的处理器。这很不好,但这不是火花的目的。
我为您准备了一个技巧。可能需要对其进行一些改进,但是您会有想法。这就是我要做的。从路径列表中,我将提取镶木地板文件的所有架构,并创建一个收集所有列的新的大型架构。然后,我将要求spark使用此模式读取所有实木复合地板文件(不存在的列将自动设置为null)。然后,我将合并所有数据帧并在此大数据帧上执行转换,最后使用partitionBy
将数据帧存储在单独的文件中,同时仍然并行执行所有操作。看起来像这样。
// let create two sample datasets with one column in common (id)
// and two different columns x != y
val d1 = spark.range(3).withColumn("x", 'id * 10)
d1.show
+---+----+
| id| x |
+---+----+
| 0| 0|
| 1| 10|
| 2| 20|
+---+----+
val d2 = spark.range(2).withColumn("y", 'id cast "string")
d2.show
+---+---+
| id| y|
+---+---+
| 0| 0|
| 1| 1|
+---+---+
// And I store them
d1.write.parquet("hdfs:///tmp/d1.parquet")
d2.write.parquet("hdfs:///tmp/d2.parquet")
// Now let's create the big schema
val paths = Seq("hdfs:///tmp/d1.parquet", "hdfs:///tmp/d2.parquet")
val fields = paths
.flatMap(path => spark.read.parquet(path).schema.fields)
.toSet //removing duplicates
.toArray
val big_schema = StructType(fields)
// and let's use it
val dfs = paths.map{ path =>
spark.read
.schema(big_schema)
.parquet(path)
.withColumn("path", lit(path.split("/").last))
}
// The we are ready to create one big dataframe
dfs.reduce( _ unionAll _).show
+---+----+----+----------+
| id| x| y| file|
+---+----+----+----------+
| 1| 1|null|d1.parquet|
| 2| 2|null|d1.parquet|
| 0| 0|null|d1.parquet|
| 0|null| 0|d2.parquet|
| 1|null| 1|d2.parquet|
+---+----+----+----------+
但是,我不建议在许多数据帧上使用unionAll
。由于spark对执行计划进行了分析,因此对于许多数据帧而言,它可能非常慢。我会使用RDD版本,尽管它比较冗长。
val rdds = sc.union(dfs.map(_.rdd))
// let's not forget to add the path to the schema
val big_df = spark.createDataFrame(rdds,
big_schema.add(StructField("path", StringType, true)))
transform(big_df)
.write
.partitionBy("path")
.parquet("hdfs:///tmp/processed.parquet")
看看我处理过的目录,我得到了:
hdfs:///tmp/processed.parquet/_SUCCESS
hdfs:///tmp/processed.parquet/path=d1.parquet
hdfs:///tmp/processed.parquet/path=d2.parquet
答案 1 :(得分:0)
您应该在此处使用一些变量。最重要的是:CPU内核,每个DF的大小以及少量使用期货。提议是决定要处理的每个DF的优先级。您可以使用FAIR配置,但这还不够,并且并行处理所有进程可能会占用群集的很大一部分。您必须为DF分配优先级,并使用Future pooll控制应用程序中运行的并行作业的数量。