coalesce()方法和repartition()方法

coalesce()方法和repartition()方法

1、coalesce()方法
def coalesce(numPartitions: Int, shuffle: Boolean = false)(implicit ord: Ordering[T] = null)
    : RDD[T] = withScope {
  if (shuffle) { 
  }
  else {
  }
}

返回一个经过简化到numPartitions个分区的新RDD。这会产生一个窄依赖,例如:你将1000个分区转换成100个分区,这个过程不会发生shuffle,相反如果10个分区转换成100个分区将会发生shuffle。如果想要减少分区数,考虑使用coalesce,这样可以避免执行shuffle。

2、repartition()方法
def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T] = withScope {
  coalesce(numPartitions, shuffle = true)
}

返回一个恰好有numPartitions个分区的RDD,可以增加或者减少此RDD的并行度。内部使用shuffle重新分布数据。

由上面的源码比较可知:
coalesce()方法的参数shuffle默认设置为false,repartition()方法就是coalesce()方法shuffle为true的情况。

so:

假设RDD有N个分区,需要重新划分成M个分区:

(1)N < M: 一般情况下N个分区有数据分布不均匀的状况,利用HashPartitioner函数将数据重新分区为M个,这时需要将shuffle设置为true。因为重分区前后相当于宽依赖,会发生shuffle过程,此时可以使用coalesce(shuffle=true),或者直接使用repartition()。

(2)如果N > M并且N和M相差不多(假如N是1000,M是100): 那么就可以将N个分区中的若干个分区合并成一个新的分区,最终合并为M个分区,这是前后是窄依赖关系,可以使用coalesce(shuffle=false)。

(3)如果 N > M并且两者相差悬殊: 这时如果将shuffle设置为false,父子RDD是窄依赖关系,他们同处在一个Stage中,就可能造成spark程序的并行度不够,从而影响性能,如果在M为1的时候,为了使coalesce之前的操作有更好的并行度,可以将shuffle设置为true。

如果传入的参数大于现有的分区数目,而shuffle为false,RDD的分区数不变,也就是说不经过shuffle,是无法将RDD的分区数变多的。

3、小分区合并问题

在使用Spark进行数据处理的过程中,常常会使用filter方法来对数据进行一些预处理,过滤掉一些不符合条件的数据。在使用该方法对数据进行频繁过滤或者过滤掉的数据量过大的情况下就会造成大量小分区的生成。在Spark内部会对每一个分区分配一个task执行,如果task过多,那么每个task处理的数据量很小,就会造成线程频繁的在task之间切换,使得资源开销较大,且很多任务等待执行,并行度不高,这会造成集群工作效益低下。

为了解决这一个问题,常采用RDD中重分区的函数(coalesce函数或repartition函数)来进行数据紧缩,减少分区数量,将小分区合并为大分区,从而提高效率。

关于宽依赖(发生shuffle)和窄依赖(不发生shuffle)

  • 窄依赖:父Rdd的分区最多只能被一个子Rdd的分区所引用,即一个父Rdd的分区对应一个子Rdd的分区,或者多个父Rdd的分区对应一个子Rdd的分区。即一对一或多对一,如下图左边所示。
  • 宽依赖:RDD的分区依赖于父RDD的多个分区或所有分区,即存在一个父RDD的一个分区对应一个子RDD的多个分区。1个父RDD分区对应多个子RDD分区,这其中又分两种情况:1个父RDD对应所有子RDD分区(未经协同划分的Join)或者1个父RDD对应非全部的多个RDD分区(如groupByKey)。即一对多。
    窄依赖和宽依赖
4、repartition和partitionBy的区别

repartition 和 partitionBy 都是对数据进行重新分区,默认都是使用 HashPartitioner,区别在于partitionBy 只能用于 PairRdd,但是当它们同时都用于 PairRdd时,结果却不一样:

scala>import org.apache.spark.HashPartitioner 
import org.apache.spark.HashPartitioner 

scala>val rdd=sc.parallelize(Array(("a",1),("a",2),("b",1),("b",3),("c",1),("e",5)),2)
rdd: org.apache.spark.rdd.RDDC(String, Int)]=ParallelcollectionRDD[14] at parallelize at<console>:28

scala>rdd.repartition(4).glom().collect()
res4: Array[Array[(String, Int)]]=Array(Array((a,2),(c,1)), Array((b,1),(e,5)), Array(), Array((a,1),(b,3)))

scala>rdd.partitionBy(new HashPartitioner(4)).glom().collect()
res5: Array[Array[(String, Int)]]=Array(Array(), Array((a,1),(a,2),(e,5)), Array((b,1),(b,3)), Array((c,1)))

查看源码可知,partitionBy使用给定的key执行HashPartitionner,而repartition使用了一个随机生成的数来当做 Key,而不是使用原来的 Key。

HashPartitioner是如何分区的: Uses Java’s Object.hashCodemethod to determine the partition as partition = key.hashCode() % numPartitions.
翻译过来就是使用java对象的hashCode来决定是哪个分区,对于piarRDD, 分区就是key.hashCode() % numPartitions。
3%3=0,所以 (3,6) 这个元素在0分区, 4%3=1,所以元素(4,8) 在1分区。 下面参考一张图:
partition

注:对(“a”,1),(“a”,2),(“b”,1),(“b”,3),(“c”,1),(“e”,5)的分区,是采用a(65),b,c,e的ASCII码值对4取余,a,e取余是1,b取余为2,从取余为3,分区0没有数据。

so,Choosing the right number of partitions is important …

猜你喜欢

转载自blog.csdn.net/olizxq/article/details/82808412