这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战
一、分区
spark.default.parallelism
: (默认的并发数) = 2
当配置文件 spark-default.conf
中没有显示的配置, 则按照如下规则取值:
- 本地模式
-- spark.default.parallelism = N
spark-shell --master local[N]
-- spark.default.parallelism = 1
spark-shell --master local
复制代码
- 伪分布式
-- spark.default.parallelism = x * y
-- x 为本机上启动的executor数
-- y为每个executor使用的core数
-- z为每个 executor使用的内存
spark-shell --master local-cluster[x,y,z]
复制代码
- 分布式模式(
yarn
&standalone
)
spark.default.parallelism = max(应用程序持有executor的core总数, 2)
复制代码
SparkContext
初始化时, 同时会生成两个参数, 由上面得到的 spark.default.parallelism
推导出这两个参数的值
// 从集合中创建RDD的分区数
sc.defaultParallelism = spark.default.parallelism
// 从文件中创建RDD的分区数
sc.defaultMinPartitions = min(spark.default.parallelism, 2)
复制代码
创建 RDD
的两种种方式:
- 通过集合创建
备注: 简单的说
RDD
分区数等于cores
总数
// 如果创建RDD时没有指定分区数, 则rdd的分区数 = sc.defaultParallelism
val rdd = sc.parallelize(1 to 100)
rdd.getNumPartitions
复制代码
- 通过
textFile
创建
如果没有指定分区数:
- 本地文件,
rdd
的分区数 = max(本地文件分片数, sc.defaultMinPartitions)HDFS
文件,rdd
的分区数 = max(hdfs文件 block 数, sc.defaultMinPartitions)
val rdd = sc.textFile("data/start0721.big.log")
rdd.getNumPartitions
复制代码
备注:
- 本地文件分片数 = 本地文件大小 / 32M
- 如果读取的是
HDFS
文件, 同时指定的分区数 <hdfs
文件的block
数, 指定的数不生效。
二、分区器
提问,以下 RDD
分别是否有分区器, 是什么类型的分区器?
val rdd1 = sc.textFile("/wcinput/wc.txt")
rdd1.partitioner
val rdd2 = rdd1.flatMap(_.split("\\s+"))
rdd2.partitioner
val rdd3 = rdd2.map((_, 1))
rdd3.partitioner
val rdd4 = rdd3.reduceByKey(_+_)
rdd4.partitioner
val rdd5 = rdd4.sortByKey()
rdd5.partitioner
复制代码
只有 Key-Value
类型的 RDD
才可能有分区器, Value
类型的 RDD
分区器的值是 None
。
接下来详细介绍分区器。
(1)分区器分类
分区器的作用及分类:
在
PairRDD(key,value)
中, 很多操作都是基于key
的, 系统会按照key
对数据进行重组, 如groupbykey
; 数据重组需要规则, 最常见的就是基于Hash
的分区, 此外还有一种复杂的基于抽样Range
分区方法;
主要分区器有:
HashPartitioner
RangePartitioner
- 自定义分区器
1)HashPartitioner
HashPartitioner
: 最简单、最常用, 也是默认提供的分区器。对于给定的 key
, 计算其 hashCode
, 并除以分区的个数取余, 如果余数小于0, 则用 余数+分区的个数, 最后返回的值就是这个 key
所属的分区 ID
。
该分区方法可以保证:
key
相同的数据出现在同一个分区中。- 用户可通过
partitionBy
主动使用分区器, 通过partitions
参数指定想要分区的数量。
val rdd1 = sc.makeRDD(1 to 100).map((_, 1))
rdd1.getNumPartitions
// 仅仅是将数据大致平均分成了若干份;rdd并没有分区器
rdd2.glom.collect.foreach(x=>println(x.toBuffer))
rdd1.partitioner
// 主动使用 HashPartitioner
val rdd2 = rdd1.partitionBy(new org.apache.spark.HashPartitioner(10))
rdd2.glom.collect.foreach(x=>println(x.toBuffer))
// 主动使用 RangePartitioner
val rdd3 = rdd1.partitionBy(new org.apache.spark.RangePartitioner(10, rdd1))
rdd3.glom.collect.foreach(x=>println(x.toBuffer))
复制代码
2)RangePartitioner
RangePartitioner
: 简单的说就是将一定范围内的数映射到某一个分区内。在实现中, 分界的算法尤为重要, 用到了水塘抽样算法。
sortByKey
会使用RangePartitioner
。
如图:
现在的问题: 在执行分区之前其实并不知道数据的分布情况, 如果想知道数据分区就需要对数据进行采样?
Spark
中 RangePartitioner
在对数据采样的过程中使用了水塘采样算法。
水塘采样: 从包含
n
个项目的集合S
中选取k
个样本, 其中n
为一很大或未知的数量, 尤其适用于不能把所有n
个项目都存放到主内存的情况; 在采样的过程中执行了collect()
操作, 引发了Action
操作。
3)自定义分区器
Spark
允许用户通过自定义的 Partitioner
对象, 灵活的来控制 RDD
的分区方式。
实现自定义分区器按以下规则分区:
- 分区0 < 100
- 100 <= 分区1 < 200
- 200 <= 分区2 < 300
- 300 <= 分区3 < 400
- ... ...
- 900 <= 分区9 < 1000