Spark:RDD分区数和分区器
两个概念:
- 分区partition
- 分区器partitioner
partition
RDD有个partitions方法:
final def partitions: Array[Partition]
,
能够返回一个数组,数组元素是RDD的partition。
partition是RDD的最小数据处理单元,可以看作是一个数据块,每个partition有个编号index。
一个partition被一个map task处理。
partitioner
MR任务的map阶段的处理结果会进行分片(也可以叫分区,这个分区不同于上面的分区),分片的数量就是reduce task的数量。
具体怎么分片由分区器partitioner决定,spark中默认定义了两种partitioner:
- 哈希分区器(Hash Partitioner)
- 范围分区器(Range Partitioner)
hash分区器会根据key-value的键值key的hashcode进行分区,速度快,但是可能产生数据偏移,造成每个分区中数据量不均衡。
range分区器会对现有rdd中的key-value数据进行抽样,尽量找出均衡分割点,一定程度上解决了数据偏移问题,力求分区后的每个分区内数据量均衡,但是速度相对慢。
partitioner分区详情
在对父RDD执行完Map阶段任务后和在执行Reduce阶段任务前,会对Map阶段中间结果进行分区。
分区由父RDD的partitioner确定,主要包括两部分工作:
- 确定分区数量(也就是reduce task数量),也是子RDD的partition数量。
- 决定将Map阶段中间结果的每个key-value对分到哪个分区上。
假设一个父RDD要执行reduceByKey任务,我们可以显式的指定分区器:
val rdd_child = rdd_parent.reduceByKey(new HashPartitioner(3), _+_)
HashPartitioner
构造参数3就是分区数量,也是启动的reduce task数量,也是reduceByKey
结果返回的子RDD的partitions
方法返回的数组的长度。
如果没有显式指定分区器,则会调用org.apache.spark
包下伴生对象Partitioner
的defaultPartitioner
静态方法返回的分区器作为默认分区器。
defaultPartitioner
返回默认分区器的过程如下:
尝试利用父RDD的partitioner,如果父RDD没有partitioner,则会查看sparkConf中是否定义了 spark.default.parallelism
配置参数,如果定义了就返回new HashPartitioner(sc.defaultParallelism)
作为默认分区器,如果没定义就返回new HashPartitioner(rdd_parent.partitions.length)
作为默认分区器——
以下是源码:
//org.apache.spark包下伴生对象object Partitioner的方法
def defaultPartitioner(rdd: RDD[_], others: RDD[_]*): Partitioner = {
val rdds = (Seq(rdd) ++ others)
val hasPartitioner = rdds.filter(_.partitioner.exists(_.numPartitions > 0))
if (hasPartitioner.nonEmpty) {
hasPartitioner.maxBy(_.partitions.length).partitioner.get
} else {
if (rdd.context.conf.contains("spark.default.parallelism")) {
new HashPartitioner(rdd.context.defaultParallelism)
} else {
new HashPartitioner(rdds.map(_.partitions.length).max)
}
}
}
更具体的,无论是以本地模式、Standalone 模式、Yarn 模式或者是 Mesos 模式来运行 Apache Spark,分区的默认个数等于对spark.default.parallelism
的指定值,若该值未设置,则 Apache Spark 会根据不同集群模式的特征,来确定这个值。