Spark大数据处理学习笔记3.3 掌握RDD分区

一、RRD分区

(一)RDD分区概念

  • RDD是一个大的数据集合,该集合被划分成多个子集合分布到了不同的节点上,而每一个子集合就称为分区(Partition)。因此,也可以说,RDD是由若干个分区组成的。
    在这里插入图片描述

(二)RDD分区作用

  • 在分布式程序中,网络通信的开销是很大的,因此控制数据分布以获得最少的网络传输可以极大的提升程序的整体性能,Spark程序可以通过控制RDD分区方式来减少通信开销。Spark中所有的RDD都可以进行分区,系统会根据一个针对键的函数对元素进行分区。虽然Spark不能控制每个键具体划分到哪个节点上,但是可以确保相同的键出现在同一个分区上。

二、RDD分区数量

(一)RDD分区原则

  • RDD各个分区中的数据可以并行计算,因此分区的数量决定了并行计算的粒度。Spark会给每一个分区分配一个单独的Task任务对其进行计算,因此并行Task的数量是由分区的数量决定的。RDD分区的一个分区原则是使得分区的数量尽量等于集群中CPU核心数量。

(二)影响分区的因素

  • RDD的创建有两种方式:一种是使用parallelize()方法从对象集合创建;另一种是使用textFile()方法从外部存储系统创建。而RDD分区的数量与RDD的创建方式以及Spark集群的运行模式有关。

(三)使用parallelize()方法创建RDD时的分区数量

1、指定分区数量

  • 使用parallelize()方法创建RDD时,可以传入第二个参数,指定分区数量。

  • 分区的数量应尽量等于集群中所有CPU的核心总数,以便可以最大程度发挥CPU的性能。
  • 利用mapPartitionsWithIndex()函数实现带分区索引的映射

  • 第1个分区完成了3个元素的映射,第2个分区完成了3个元素的映射,第3个分区完成了4个元素的映射

2、默认分区数量

  • 若不指定分区数量,则默认分区数量为Spark配置文件spark-defaults.conf中的参数spark.default.parallelism的值。若没有配置该参数,则Spark会根据集群的运行模式自动确定分区数量。
  • 如果是本地模式,默认分区数量就等于本机CPU核心总数,这样每个CPU核心处理一个分区的计算任务,可以最大程度发挥CPU的性能。
  • 如果是Spark Standalone或Spark On YARN模式,默认分区数量就取集群中所有CPU的核心总数与2中的较大值,即最少分区数为2。
    在这里插入图片描述

(四)RDD分区方式

  • Spark框架为RDD提供了两种分区方式,分别是哈希分区(HashPartitioner)范围分区(RangePartitioner)。其中,哈希分区是根据哈希值进行分区;范围分区是将一定范围的数据映射到一个分区中。这两种分区方式已经可以满足大多数应用场景的需求。与此同时,Spark也支持自定义分区方式,即通过一个自定义的Partitioner对象来控制RDD的分区,从而进一步减少通信开销。

(五)使用textFile()方法创建RDD时的分区数量

  • textFile()方法通常用于读取HDFS中的文本文件,使用该方法创建RDD时,Spark会对文件进行分片操作(类似于MapReduce的分片,实际上调用的是MapReduce的分片接口),分片操作完成后,每个分区将存储一个分片的数据,因此分区的数量等于分片的数量
    在这里插入图片描述

1、指定最小分区数量

  • 使用textFile()方法创建RDD时可以传入第二个参数指定最小分区数量。最小分区数量只是期望的数量,Spark会根据实际文件大小、文件块(Block)大小等情况确定最终分区数量

 

 在HDFS中有一个文件/park/test.txt,读取该文件,并指定最小分区数量为9,但是实际分区数量是10

三、Spark分区器

(一)分区器 - Partitioner抽象类

  • Spark RDD的Shuffle过程与MapReduce类似,涉及数据重组和重新分区,且要求RDD的元素必须是(key, value)形式的。分区规则是由分区器(Partitioner)控制的,Spark的主要分区器是HashPartitioner和RangePartitioner,都继承了抽象类Partitioner。
    在这里插入图片描述
  • 抽象类Partitioner中有两个方法,分别用于指定分区数量和设置分区规则
    在这里插入图片描述

(二)哈希分区器 - HashPartitioner类

  • HashPartitioner是Spark使用的默认分区器,其分区规则为:取(key,value)对中key的hashCode值,然后除以分区数量后取余数。若余数小于0(一般余数都大于等于0),则用余数与分区数量的和作为分区ID,否则将余数作为分区ID。分区ID一致的(key,value)对则会被分配到同一个分区。因此,默认情况下,key值相同的(key,value)对一定属于同一个分区,但是同一个分区中可能有多个key值不同的(key,value)对。该分区器还支持key值为null的情况,当key值等于null时,将直接返回0作为分区ID。
  • HashPartitioner分区器中,对key取hashCode值实际上调用的是Java类Object中的hashCode()方法。由于Java数组的hashCode值基于的是数组标识,而不是数组内容,因此具有相同内容的数组的hashCode值不同。如果将数组作为RDD的key,就可能导致内容相同的key不能分配到同一个分区中。这个时候可以将数组转为集合,或者使用自定义分区器,根据数组内容进行分区。

(二)哈希分区器 - HashPartitioner类

  • HashPartitioner是Spark使用的默认分区器,其分区规则为:取(key,value)对中key的hashCode值,然后除以分区数量后取余数。若余数小于0(一般余数都大于等于0),则用余数与分区数量的和作为分区ID,否则将余数作为分区ID。分区ID一致的(key,value)对则会被分配到同一个分区。因此,默认情况下,key值相同的(key,value)对一定属于同一个分区,但是同一个分区中可能有多个key值不同的(key,value)对。该分区器还支持key值为null的情况,当key值等于null时,将直接返回0作为分区ID。
  • HashPartitioner分区器中,对key取hashCode值实际上调用的是Java类Object中的hashCode()方法。由于Java数组的hashCode值基于的是数组标识,而不是数组内容,因此具有相同内容的数组的hashCode值不同。如果将数组作为RDD的key,就可能导致内容相同的key不能分配到同一个分区中。这个时候可以将数组转为集合,或者使用自定义分区器,根据数组内容进行分区。

四、自定义分区器

(一)提出问题

  • 在有些情况下,使用Spark自带的分区器满足不了特定的需求。
  • 例如,某学生有以下3科成绩数据:
科目成绩
chinese98
math88

english

96
chinese89
math96

english

67
chinese88
math78

english

89
  • 现需要将每一科成绩单独分配到一个分区中,然后将3科成绩输出到HDFS的指定目录(每个分区对应一个结果文件),此时就需要对数据进行自定义分区。

(二)解决问题

1、准备数据文件

在master虚拟机的/home 文件上创建marks.txt文件

 上传到dfs

2、新建自定义分区器

  • 在net.zhj.rdd.day04中创建SubjectPartitioner类

package net.zhj.rdd.day04

import org.apache.spark.Partitioner

/**
 * 自定义分区器
 */
class CustomPartitioner(partitions:Int) extends Partitioner{
  
  override def numPartitions: Int = partitions

  override def getPartition(key: Any): Int = {
    val partitionIndex = key.toString match {
      case "chinese" => 0
      case "math" => 1
      case "english" =>2
    }
    partitionIndex
  }
}

3、使用科目分区器

  • 调用RDD的partitionBy()方法传入自定义分区器类MyPartitioner的实例,可以对RDD按照自定义规则进行重新分区。
  • 创建TestSubjectPartitioner单例对象

package net.zhj.rdd.day04

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

object TestSubjectPartitioner {
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf()
      .setAppName("TestSubjectPartitioner")
      .setMaster("local[*]")
    val sc = new SparkContext(conf)
    val lines  = sc.textFile("hdfs://master1:9000/partition/input/marks.txt")
    val data:RDD[(String,Int)] = lines.map(line => {
      val fields= line.split(" ")
      (fields(0),fields(1).toInt)
    })
    val partitionData = data.partitionBy(new SubjectPartitioner(3))
      partitionData.collect.foreach(println)
      partitionData.saveAsTextFile("hdfs://master1:9000/partition/output")
  }
}

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值