Key-value类型的RDD数据分区方式你了解吗

232 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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分区情况可能如下图所示,极容易出现数据倾斜问题。

image.png

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
        }
    }

}