【Spark】RDD分区

这是我参与11月更文挑战的第15天,活动详情查看:2021最后一次更文挑战

一、分区

spark.default.parallelism : (默认的并发数) = 2

当配置文件 spark-default.conf 中没有显示的配置, 则按照如下规则取值:

  1. 本地模式
-- spark.default.parallelism = N
spark-shell --master local[N] 

-- spark.default.parallelism = 1
spark-shell --master local
复制代码
  1. 伪分布式
-- spark.default.parallelism = x * y
-- x 为本机上启动的executor数
-- y为每个executor使用的core数
-- z为每个 executor使用的内存
spark-shell --master local-cluster[x,y,z]
复制代码
  1. 分布式模式(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 的两种种方式:

  1. 通过集合创建

备注: 简单的说 RDD 分区数等于 cores 总数

// 如果创建RDD时没有指定分区数, 则rdd的分区数 = sc.defaultParallelism
val rdd = sc.parallelize(1 to 100)
rdd.getNumPartitions
复制代码
  1. 通过 textFile 创建

如果没有指定分区数:

  1. 本地文件,rdd 的分区数 = max(本地文件分片数, sc.defaultMinPartitions)
  2. 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

如图: 2021-02-2017-03-02.png

现在的问题: 在执行分区之前其实并不知道数据的分布情况, 如果想知道数据分区就需要对数据进行采样?

SparkRangePartitioner 在对数据采样的过程中使用了水塘采样算法。

水塘采样: 从包含 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

猜你喜欢

转载自juejin.im/post/7031188718891302920