1、分区的介绍
分区partition,RDD内部的数据集合在逻辑上和物理上被划分成多个小子集合,这样的每一个子集合我们将其称为分区,即是数据集的一个逻辑块。
RDD只是数据集的抽象,分区内部并不会存储具体的数据。Partition 类内包含一个 index 成员,表示该分区在 RDD 内的编号,通过 RDD 编号 + 分区编号可以唯一确定该分区对应的块编号,利用底层数据存储层提供的接口,就能从存储介质(如:HDFS、Memory)中提取出分区对应的数据。
2、分区的意义
RDD 是一种分布式的数据集,数据源多种多样,而且数据量也很大,在海量数据进行计算时时,分区的个数会决定并行计算的粒度,一个partition被一个maptask处理,分区可以提高计算的并行度。
3、分区的原则
分区个数会对spark性能有影响。分区数不是越多越好,分区数太多意味着任务数太多,每次调度任务也是很耗时的,所以分区数太多会导致总体耗时增多。分区数太少的话,会导致一些结点没有分配到任务或者每个分区要处理的数据量会过大,从而对个别结点的内存要求就会提高。还有分区数不合理,会导致数据倾斜问题。
对此,默认情况下,一个rdd的分区数量是由什么来决定?
情况1、读取HDFS的文件系统时
spark读取hdfs上的数据,使用的就是hadoop的API。默认情况下,读取的文件有几个block块,就有几个分区。如果只有一个block块,那么至少有2个分区。
rdd的分区的数量 = 文件的block块 > 2 ? 文件的block块数量 : 2
情况2、计算过程中
RDD 分区的一个分配原则是:尽可能使得分区的个数,等于集群核心数目。
rdd的分区数量 = -- total-executor-cores > 实际使用的cores ? 实际使用的cores 数量: --total-executor-cores 的数量
分区的数量可以自己设置,一般合理的分区数设置为总核数的2~3倍
4、分区数的设置和获取
分区数的获取
通过 rdd.partitions.size 获取spark的rdd中分区的个数。
分区数的设置
1、全局设置
Spark中分区的默认个数等于对 spark.default.parallelism 的指定值,若该值未设置,则 Spark 会根据不同集群模式的特征,来确定这个值
2、针对某个RDD进行设置
var pairRdd = sc.parallelize(List((1,1), (5,10), (5,9), (2,4), (3,5), (3,6),(4,7), (4,8),(2,3), (1,2)))
//方法1 repartition
val value: RDD[(Int, Int)] = pairRdd.repartition(10)
//方法2 coalesce算子
val value: RDD[(Int, Int)] = pairRdd.coalesce(10,false)
//coalesce操作使用HashPartitioner进行重分区,第一个参数为重分区的数目,第二个为是否shuffle,默认情况为false。repartition操作是coalesce函数第二个参数为true的实现。如果分区的数目大于原来的分区数,那么必须指定shuffle参数为true,否则分区数不变。
注意:一般情况下增大分区数用repartition方法,减小分区数用coalesce方法。
5、分区的创建
如果读取文件时,Spark会根据数据源自动分区
在读hdfs文件时,分区数等于文件分区数。在读kafka时,rdd分区数等于kafka分区数。
计算过程中
窄依赖:子 RDD 由父 RDD 分区个数决定,例如 map 操作,父 RDD 和子 RDD 分区个数一致。
宽依赖:则由分区器(Partitioner)决定,例如 groupByKey(new HashPartitioner(2)) 或者直接 groupByKey(2) 得到的新 RDD 分区个数等于2。
6、RDD中的分区器
哈希分区器(Hash Partitioner)
Hash Partitioner会根据key-value的键值key的hashcode进行分区,速度快,但是可能产生数据偏移,造成每个分区中数据量不均衡。
分区方式:partition = key.hashCode () % numPartitions
范围分区器(Range Partitioner)
范围分区器会对现有rdd中的key-value数据进行抽样,尽量找出均衡分割点,力求分区后的每个分区内数据量均衡,但是速度相对慢。
1. 先进行数据抽样,对抽样数据进行排序后得到分区的边界数据
水塘抽样算法原理:随机算法之水塘抽样算法_fe_lucifer的博客-CSDN博客(选择性了解)
2. 根据key在边界数据中所属的位置来判断分区的id
可以理解为,假设分区的数量为3,key的范围是0~10000,那么第一个分区的范围就是0~3333,第二个分区的范围是3334~6666,第三个分区的范围是6667~10000。使用这个范围,将范围内的键分配给相应的分区。这种方法适用于键中有自然排序,键不为负。
注意:采样中需要collect,所以会触发action。sort和sortByKey算子用到了Range Partitioner,所以尽管sort和sortByKey是transformation算子,但也回触发一个job。
自定义分区器(需要 extends Partitioner)
//自定义的分区器
class MyPartition(subjects:Array[String]) extends Partitioner {
//把学科从0开始加编号
private val index: Array[(String, Int)] = subjects.zipWithIndex
//为了方便根据对应的学科取编号,转为Map
private val subsAndIndex: Map[String, Int] = index.toMap
//分区的数量
override def numPartitions: Int = subjects.length
//传递一个key的值,返回分区编号
override def getPartition(key: Any): Int = {
//学科和分区的对应关系
//把传递的key进行类型强转
val subjectAndTeacher = key.asInstanceOf[(String, String)]
//获取到学科的名称
val subject = subjectAndTeacher._1
//直接从map中取值,取到的值就是对应的编号
subsAndIndex(subject)
}
}