Spark Core快速入门系列(10) | Key-Value 类型 RDD 的数据分区器

  大家好,我是不温卜火,是一名计算机学院大数据专业大二的学生,昵称来源于成语—不温不火,本意是希望自己性情温和。作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新。但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!暂时只有csdn这一个平台,博客主页:https://buwenbuhuo.blog.csdn.net/

  此篇为大家带来的是Key-Value 类型 RDD 的数据分区器
1


2

  对于只存储 value的 RDD, 不需要分区器.
  只有存储Key-Value类型的才会需要分区器.
  Spark 目前支持 Hash 分区和 Range 分区,用户也可以自定义分区.
  Hash 分区为当前的默认分区,Spark 中分区器直接决定了 RDD 中分区的个数、RDD 中每条数据经过 Shuffle 过程后属于哪个分区和 Reduce 的个数.

一. 查看 RDD 的分区

1. value RDD 的分区器

scala> val rdd1 = sc.parallelize(Array(10))
rdd1: org.apache.spark.rdd.RDD[Int] = ParallelCollectionRDD[2] at parallelize at <console>:25

scala> rdd1.partitioner
res8: Option[org.apache.spark.Partitioner] = None

2. key-value RDD 的分区器

scala> val rdd1 = sc.parallelize(Array(("hello", 1), ("world", 1)))
rdd1: org.apache.spark.rdd.RDD[(String, Int)] = ParallelCollectionRDD[3] at parallelize at <console>:25

scala> rdd1.partitioner
res11: Option[org.apache.spark.Partitioner] = None

// 导入HashPartitioner
import org.apache.spark.HashPartitioner

// 对 rdd1 重新分区, 得到分区后的 RDD, 分区器使用 HashPartitioner
scala> val rdd2 = rdd1.partitionBy(new HashPartitioner(3))
rdd2: org.apache.spark.rdd.RDD[(String, Int)] = ShuffledRDD[5] at partitionBy at <console>:27

scala> rdd2.partitioner
res14: Option[org.apache.spark.Partitioner] = Some(org.apache.spark.HashPartitioner@3)

二. HashPartitioner

  HashPartitioner分区的原理:对于给定的key,计算其hashCode,并除以分区的个数取余,如果余数小于 0,则用余数+分区的个数(否则加0),最后返回的值就是这个key所属的分区ID。
3

  • 1. 代码
package Day04
import org.apache.spark.rdd.RDD
import org.apache.spark.{HashPartitioner, SparkConf, SparkContext}

/**
 **
@author 不温卜火
 **
 * @create 2020-07-27 11:20
 **
 *         MyCSDN :https://buwenbuhuo.blog.csdn.net/
 */
object Test {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Practice").setMaster("local[2]")
    val sc = new SparkContext(conf)
    val rdd1 = sc.parallelize(Array((10, "a"), (20, "b"), (30, "c"), (40, "d"), (50, "e"), (60, "f")))
    // 把分区号取出来, 检查元素的分区情况
    val rdd2: RDD[(Int, String)] = rdd1.mapPartitionsWithIndex((index, it) => it.map(x => (index, x._1 + " : " + x._2)))

    println(rdd2.collect.mkString(","))

    // 把 RDD1使用 HashPartitioner重新分区
    val rdd3 = rdd1.partitionBy(new HashPartitioner(5))
    // 检测RDD3的分区情况
    val rdd4: RDD[(Int, String)] = rdd3.mapPartitionsWithIndex((index, it) => it.map(x => (index, x._1 + " : " + x._2)))
    println(rdd4.collect.mkString(","))


  }
}

  • 2. 运行结果

4

三. RangePartitioner

  HashPartitioner 分区弊端: 可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有 RDD 的全部数据。比如我们前面的例子就是一个极端, 他们都进入了 0 分区.
  RangePartitioner 作用:将一定范围内的数映射到某一个分区内,尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大,但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内。实现过程为:
  第一步:先从整个 RDD 中抽取出样本数据,将样本数据排序,计算出每个分区的最大 key 值,形成一个Array[KEY]类型的数组变量 rangeBounds;(边界数组).
  第二步:判断key在rangeBounds中所处的范围,给出该key值在下一个RDD中的分区id下标;该分区器要求 RDD 中的 KEY 类型必须是可以排序的.
  比如[1,100,200,300,400],然后对比传进来的key,返回对应的分区id。

四. 自定义分区器

  要实现自定义的分区器,你需要继承 org.apache.spark.Partitioner, 并且需要实现下面的方法:

  1. numPartitions
    该方法需要返回分区数, 必须要大于0.

  2. getPartition(key)
    返回指定键的分区编号(0到numPartitions-1)。

  3. equals
    Java 判断相等性的标准方法。这个方法的实现非常重要,Spark 需要用这个方法来检查你的分区器对象是否和其他分区器实例相同,这样 Spark 才可以判断两个 RDD 的分区方式是否相同

  4. hashCode
    如果你覆写了equals, 则也应该覆写这个方法.

  • 1.MyPartitioner
package Day04
import org.apache.spark.rdd.RDD
import org.apache.spark.{Partitioner, SparkConf, SparkContext}

/**
 **
@author 不温卜火
 **
 * @create 2020-07-27 11:54
 **
 *         MyCSDN :https://buwenbuhuo.blog.csdn.net/
 */
/*
使用自定义的 Partitioner 是很容易的 :只要把它传给 partitionBy() 方法即可。

Spark 中有许多依赖于数据混洗的方法,比如 join() 和 groupByKey(),
它们也可以接收一个可选的 Partitioner 对象来控制输出数据的分区方式。
*/
object MyPartitionerDemo {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setAppName("Practice").setMaster("local[*]")
    val sc = new SparkContext(conf)

    val rdd1 = sc.parallelize(
      Array((10, "a"), (20, "b"), (30, "c"), (40, "d"), (50, "e"), (60, "f")),
      3)
    val rdd2: RDD[(Int, String)] = rdd1.partitionBy(new MyPartitioner(4))
    val rdd3: RDD[(Int, String)] = rdd2.mapPartitionsWithIndex((index, items) => items.map(x => (index, x._1 + " : " + x._2)))
    println(rdd3.collect.mkString(" "))

  }
}

class MyPartitioner(numPars: Int) extends Partitioner {
  override def numPartitions: Int = numPars


  override def getPartition(key: Any): Int = {
    1
  }
}


  • 2. 结果

5
  本次的分享就到这里了,


14

  好书不厌读百回,熟读课思子自知。而我想要成为全场最靓的仔,就必须坚持通过学习来获取更多知识,用知识改变命运,用博客见证成长,用行动证明我在努力。
  如果我的博客对你有帮助、如果你喜欢我的博客内容,请“点赞” “评论”“收藏”一键三连哦!听说点赞的人运气不会太差,每一天都会元气满满呦!如果实在要白嫖的话,那祝你开心每一天,欢迎常来我博客看看。
  码字不易,大家的支持就是我坚持下去的动力。点赞后不要忘了关注我哦!

15
16

猜你喜欢

转载自blog.csdn.net/qq_16146103/article/details/107605775