- coalesce提高性能!
- coalesce不要滥用!
上面俩货是网上最常见的对coalesce的评价,话也不多说,直接让我们一起来站在源码的角度,分析一下coalesce
这个东西
首先一起来看一下coalesce
的定义:
def coalesce(numPartitions: Int, shuffle: Boolean = false,
partitionCoalescer: Option[PartitionCoalescer] = Option.empty)
(implicit ord: Ordering[T] = null)
: RDD[T] = withScope {
if (shuffle) {
/** 就是一个重分区的函数,打散分区的操作. */
val distributePartition = ...
new CoalescedRDD(
new ShuffledRDD(mapPartitionsWithIndex(distributePartition), ...).values
} else {
new CoalescedRDD(this, numPartitions, partitionCoalescer)
}
}
上面有几点需要注意的:
- 若shuffle为false:生成一个
CoalescedRDD
,并依赖于当前RDD - 反之,先生成一个依赖于当前RDD的
ShuffledRDD
,然后再生成CoalescedRDD
-
distributePartition
是打散分区的、partitionCoalescer
是说怎样合并分区,这俩先不做关注
----------------------------------------------------------------------------------------------另外,还需要明确一个重要的点:DAGScheduler创建Task的数量取决于Stage的最后一个RDD的分区数,可以参考下面的源码(DAGScheduler划分Stage调用过程中会调用createShuffleMapStage
方法):
def createShuffleMapStage(shuffleDep: ShuffleDependency[_, _, _], jobId: Int): ShuffleMapStage = {
val rdd = shuffleDep.rdd
val numTasks = rdd.partitions.length
...
val stage = new ShuffleMapStage(
id, rdd, numTasks, ...)
...
stage
}
可以看到,stage封装了最后一个rdd的分区数量`numTasks`
-----------------------------------------------------------------------------------------------
我们所说的coalesce
提升性能,是在某些特定场景下可能会提高计算速度比如filter.coalesce
,因为filter
之后可能会出现数据不均匀的现象,通过 coalesce
合并分区达到提高利用率等效果。同样的,这种情况下coalesce
使用不当也可能造成OOM等意外,原因正是上面说到的每个stage任务数量取决于coalesce的传的数字,本来这个stage需要2000个task可以正常运行,但是由于coalesce
存在,硬生生的把整个stage的task数量降低了,每个task计算数据成倍的增长,进而导致影响性能甚至是crash。
说完如何影响性能,其实这里也可以解释repartition
与他的区别:
repartition
其实就是令shuffle
为true,可以看到,这种情况下,生成的是2个RDD,第一个ShuffleRDD
与this
产生宽依赖,其实也就是将coalesce
操作推到下一个stage
的最开始了,意思就是下一个Stage爱多少个task就多少,别影响我上一个Stage的task数量就行,这也是减少分区的另一种办法。最后还是,存在即合理,两种算子合理的运用才能写出优质的Spark程序。