持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情
之前我们说过Key-value类型中的转换算子有分区算子 partitionByKey 以及 groupByKey可以指定分区器(默认使用的是HashPartitioner),那么这篇文章我就来简单介绍下 Key-value类型的RDD中的数据分区方式。
Hash分区
HashPartitioner分区的原理: 对于给定的key,计算其hashCode,并除以分区的个数取余。如果余数小于0,则用余数+分区的个数(否则加0),最后返回的值就是这个key所属的分区ID。
注意:HashPartitioner分区存在弊端,可能会导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据,造成数据倾斜。
例如: 对于以下实例我按照单词首字母的 hashCode值 % 分区个数 进行分区,假设有3个分区(0,1,2),那么HashPartition分区情况可能如下图所示,极容易出现数据倾斜问题。
Range分区
RangePartitioner的作用: 简单的说就是将一定范围内的数映射到某一个分区内,会尽量保证每个分区中数据量均匀,而且分区与分区之间是有序的,但是分区内的元素是不能保证顺序的。而且使用Range分区,一个分区中的元素肯定都是比另一个分区内的元素小或者大。
实现过程为:
第一步:先从整个RDD中采用水塘抽样算法,抽取出样本数据,将样本数据排序,计算出每个分区的最大key值,形成一个Array[KEY]类型的数组变量 rangeBounds;
第二步:判断 key 在 rangeBounds 中所处的范围,给出该key值在下一个RDD中的分区id下标;Range分区器要求RDD中的Key类型必须是可排序的。
例如:假设我们有100万条数据要分4个区,分别是 -25、 25-50、 50-75、75- ,那么我们要
-
先从100万条中抽100个数(1,2,3,.....100 )
-
对100个数进行排序,然后均匀的分为4段
-
获取100万条数据,每个值与4个分区的范围比较,放入合适分区
自定义分区
要实现自定义分区器,需要继承org.apache.spark.Partitioner类,并实现下面三个方法。
(1)numPartitions: Int: 返回创建出来的分区数。
(2)getPartition(key: Any): Int: 返回给定键的分区编号(0到numPartitions-1)。
(3)equals():Java 判断相等性的标准方法。这个方法的实现非常重要,Spark需要用这个方法来检查分区器对象是否和其他分区器实例相同,这样Spark才可以判断两个RDD的分区方式是否相同
具体实现代码如下
object KeyValue01_partitionBy {
def main(args: Array[String]): Unit = {
//1.创建SparkConf并设置App名称
val conf: SparkConf = new SparkConf().setAppName("SparkCoreTest").setMaster("local[*]")
//2.创建SparkContext,该对象是提交Spark App的入口
val sc: SparkContext = new SparkContext(conf)
//3具体业务逻辑
//3.1 创建第一个RDD
val rdd: RDD[(Int, String)] = sc.makeRDD(Array((1, "aaa"), (2, "bbb"), (3, "ccc")), 3)
//3.2 自定义分区
val rdd3: RDD[(Int, String)] = rdd.partitionBy(new MyPartitioner(2))
//4 打印查看对应分区数据
val indexRdd = rdd3.mapPartitionsWithIndex(
(index, datas) => datas.map((index,_))
)
indexRdd.collect()
//5.关闭连接
sc.stop()
}
}
// 自定义分区
class MyPartitioner(num: Int) extends Partitioner {
// 设置的分区数
override def numPartitions: Int = num
// 具体分区逻辑
override def getPartition(key: Any): Int = {
if (key.isInstanceOf[Int]) {
val keyInt: Int = key.asInstanceOf[Int]
if (keyInt % 2 == 0)
0
else
1
}else{
0
}
}
}