Spark自定义分区

Spark分区与自定义分区

1.概述

分区是RDD内部并行计算的一个计算单元,RDD的数据集在逻辑上被划分为多个分片,每一个分片称为分区,分区的格式决定了并行计算的粒度,而每个分区的数值计算都是在一个任务中进行的,因此任务的个数,也是由RDD(准确来说是作业最后一个RDD)的分区数决定。

2.分区原则

RDD分区的一个分区原则:尽可能是分区的个数等于集群核数目

无论是本地模式、Standalone模式、YARN模式或Mesos模式,我们都可以通过spark.default.parallelism来配置其默认分区个数,若没有设置该值,则根据不同的集群环境确定该值。

本地模式:默认为本地机器的CPU数目,若设置了local[N],则默认为N

Apache Mesos:默认的分区数为8。

Standalone或YARN:默认取集群中所有核数目的总和,或者2,取二者的较大值。

3.Partitioner作用

Partitioner要发挥作用必须满足的两个前提:

(1) 只有Key-Value类型的RDD才有分区的,非Key-Value类型的RDD分区的值是None
(2) 每个RDD的分区ID范围:0~numPartitions-1,决定这个值是属于那个分区的。

Partitioner是在shuffle阶段起作用,无论对于mapreduce还是spark,shuffle都是重中之重,因为shuffle的性能直接影响着整个程序,shuffle涉及到网络开销及可能导致的数据倾斜问题,是调优关注的重点。在shuffle中,map阶段处理结果使用ShuffleWriter,根据Partitioner逻辑写到不同bucket中,不同的bucket后续被不同的reducer使用.

4. Spark自带的分区

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

class HashPartitioner(partitions: Int) extends Partitioner {
  require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative.")

  def numPartitions: Int = partitions

  def getPartition(key: Any): Int = key match {
    case null => 0
    case _ => Utils.nonNegativeMod(key.hashCode, numPartitions)
  }

  override def equals(other: Any): Boolean = other match {
    case h: HashPartitioner =>
      h.numPartitions == numPartitions
    case _ =>
      false
  }

  override def hashCode: Int = numPartitions
}

RangePartitioner分区

将一定范围内的数映射到某一个分区内,在实现中,分界的算法尤为重要。算法对应的函数是rangeBounds。

HashPartitioner分区弊端:可能导致每个分区中数据量的不均匀,极端情况下会导致某些分区拥有RDD的全部数据.

RangePartitioner分区优势:尽量保证每个分区中数据量的均匀,而且分区与分区之间是有序的,一个分区中的元素肯定都是比另一个分区内的元素小或者大;但是分区内的元素是不能保证顺序的。简单的说就是将一定范围内的数映射到某一个分区内。

5. 自定义分区

模板如下:
根据host分区,一个host一个区
//只需要继承Partitioner,重写两个方法
class MyPartitioner(val num: Int) extends Partitioner {

  //这里定义partitioner个数
  override def numPartitions: Int = ???

  //这里定义分区规则
  override def getPartition(key: Any): Int = ???
}

具体示例

class HostPartitioner(ins: Array[String]) extends  Partitioner{
      var num =0
      val parMap = new mutable.HashMap[String,Int]()

      for(i<-ins){
        parMap.put(i,num)
        num+=1
      }

      override def numPartitions: Int = ins.length

      override def getPartition(key: Any): Int = {
        parMap.getOrElse(key.toString,0)
      }
}

6. 使用分区

描述: 网站url统计
元数据:
20160321101954 http://java.zhm.cn/java/course/javaeeadvanced.shtml
20160321101954 http://bigdata.zhm.cn/java/course/javaee.shtml

统计网页的某个学科的访问次数,取前2名学科

object UrlCountPartition {

  def main(args: Array[String]): Unit = {
      val conf = new SparkConf().setAppName("UrlCountPartition").setMaster("local[3]")
      val sc = new SparkContext(conf)

      //读取数据
      val rdd1 = sc.textFile("D:\\sparkdata\\input\\itcast.log").map(x=>{
        val arr = x.split("\t")
        val url = arr(1)
        (url,1)
      })

      val rdd2 = rdd1.reduceByKey(_+_)

      val rdd3 = rdd2.map(x =>{
        val url = x._1
        val host = new URL(url).getHost
        //组成两个元素的元祖
        (host,(url,x._2))
      })


    // 获取不重复的host,使用自定义Partitioner
     val arr = rdd3.map(_._1).distinct().collect()
     val partioner = new HostPartitioner(arr)


     val rdd4 = rdd3.partitionBy(partioner).mapPartitions(it =>{
       it.toList.sortBy(_._2._2).reverse.take(2).iterator
     })

    rdd4.saveAsTextFile("D:\\sparkdata\\output")
    sc.stop()
  }

}

猜你喜欢

转载自blog.csdn.net/wtzhm/article/details/80854862