spark crossjoin方法优化

场景描述

遇到的问题是 DF1.crossJoin(DF2) 执行的时间特别慢,两个 DF 的数据量大概是在千万级别,刚开始以为数据量太大导致的执行特别耗时,但后来发现在另一批同等数量级的数据上 crossJoin 是执行很快的。那这就有问题了,花时间研究了下。


原因

都是分区惹的祸。 spark 将数据按照按照分区存放,在执行运算时每个分区作为一个 task 且多个 task 并行运算从而提高处理效率。很显然分区数对计算的快慢有很大的影响。并不是分区越多执行效率越快,因为过多的分区意味着越多的 task,对这些 task 的管理也需要耗费很大的开销,所以有时反而会降低执行效率。

在资源充分利用的情况下,对于 crossJoin 方法的执行,DF 的分区越小执行效率越高。

crossJoin 结果的分区数

实验一:小数据量

当 DF 数据量较小时,crossJoin执行后的分区数等于其中一个输入 DF 的分区数。

scala> val xDF = (1 to 1000).toList.toDF("x")
scala> xDF.rdd.partitions.size
res11: Int = 64
scala> val yDF = (1 to 1000).toList.toDF("y")
scala> yDF.rdd.partitions.size
res12: Int = 64
scala> val crossJoinDF = xDF.crossJoin(yDF)
scala> crossJoinDF.rdd.partitions.size
res13: Int = 64
scala> val zDF = yDF.repartition(128)
scala> zDF.rdd.partitions.size
res5: Int = 128
scala> val xzcrossJoinDF = xDF.crossJoin(zDF)
scala> xzcrossJoinDF.rdd.partitions.size
res6: Int = 64  

实验二:大数据量

当 DF 数据量较小时,crossJoin执行后的分区数等于两个输入分区数的乘积。

scala> val xDF = (1 to 1000000).toList.toDF("x")
scala> xDF.rdd.partitions.size
res15: Int = 2
scala> val yDF = (1 to 1000000).toList.toDF("y")
scala> yDF.rdd.partitions.size
res16: Int = 2
scala> val crossJoinDF = xDF.crossJoin(yDF)
scala> crossJoinDF.rdd.partitions.size
res17: Int = 4

crossJoin 后分区是否发生变化的标准是什么呢?

我的理解:保持分每个区中数据量尽可能少,让 task 执行的快些。DF 数据本身很大时,笛卡尔积后数据量变成 M*N,增大分区数,降低每个分区中的数据量。


分区数对计算效率有什么影响?

DataFrame 的分区数对计算效率有下列影响:

  1. 当分区数很少时,计算的并行度小,没有充分利用申请到的资源,没有发挥出最大效率。
  2. 当分区数很多时,在管理小 task 上会产生很大的开销,从而降低计算效率。

crossJoin 后的 dataFrame 属于第二种,太多的分区让在 dataFrame 上的任何操作都很慢。有可能还会出现下列异常:

org.apache.spark.SparkException Job aborted due to stage failure: 
Total size of serialized results of 147936 tasks (1024.0 MB) is bigger than 
spark.driver.maxResultSize (1024.0 MB)

这个异常的原因很简单,driver 存储了所有 task 的 metadata 并且追踪 task 的执行情况,exector 执行完 task 后需要向每个 task 的状态数据传回driver,超大量的分区会产生等同量的状态信息传回 driver,结果超了 driver 的默认大小。

解决办法也很简单:使用 spark.driver.maxResultSize 提高大小就行了。但这种方式只是解决了上述异常,对计算效率的优化没有任何效果。


怎么提高计算速度?

这个也很简单:在 cross join 之前先降低输入 DF 分区数。

继续实验:

变量 数据量 分区数
df1 17,000 200
df2 15,000 200
scala> df1.count()
res73: Long = 17000
scala> df1.rdd.partitions.size
res74: Int = 200
scala> df2.count()
res75: Long = 15000
scala> df2.rdd.partitions.size
res76: Int = 200
scala> val finalDF = df1.crossJoin(df2)
scala> finalDF.rdd.partitions.size 
res77: Int = 40000
scala> time {
    
    finalDF.count()}
Elapsed time: 285163427988ns
res78: Long = 255000000

将 df1 和 df2 的分区数调整到 40

scala> val df1 = df1.repartition(40)
scala> df1.rdd.partitions.size 
res80: Int = 40
scala> val df2 = df2.repartition(40)
scala> df2.rdd.partitions.size 
res81: Int = 40
scala> val finalDF = df1.crossJoin(df2)
scala> finalDF.rdd.partitions.size 
res82: Int = 1600
scala> time {
    
    finalDF.count()}
Elapsed time: 47178149994ns
res86: Long = 255000000

可以看出:调整前花费的计算时间是调整后的 6 倍左右。


参考链接

https://towardsdatascience.com/make-computations-on-large-cross-joined-spark-dataframes-faster-6cc36e61a222

猜你喜欢

转载自blog.csdn.net/yy_diego/article/details/128568343