spark封神之路(13)-RDD分区详解

本文详细介绍了Spark中的Resilient Distributed Datasets (RDD)分区原理,包括如何通过makeRDD和textFile创建带分区的RDD,以及如何通过repartition和coalesce调整RDD的分区。内容涵盖RDD分区的默认逻辑、数据划分方法以及如何影响运算效率。同时,讨论了任务并行度的设置,如默认并行度的修改,以优化Spark作业的执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 简介

简介

理解RDD是spark中封装的用来处理数据的一种抽象概念,其主要包含处理逻辑和要处理的数据! ​ 无论是不读取文件获取的RDD还是从集合转换而来的RDD最终的目的都是为了处理对应的数据 ,数据量海量的话 , 我们应该很容易的想到让数据并行化分布式运算!牵扯到分布式那必然存在数据任务划分的问题!那么RDD在创建的时候就对数据进行了有效的分区!当然我们也可以合理的改变RDD的分区来提高运算效率!

 

一个partitioner,即RDD的分片函数。当前Spark中实现了两种类型的分片函数,一个是基于哈希的HashPartitioner,另外一个基于范围的RangePartitioner。只有对于key-value的RDD,才会有Partitioner,非key-value的RDD的Partitioner的值是None。Partitioner函数不但决定了RDD本身的分片数量,也决定了parent RDD Shuffle输出时的分片数量。

 

注意在RDD定义的时候 ,RDD是有分区的,并且这个RDD的分区确定 ,每个任务的数据也就确定了!

2 如何获取RDD的分区数据

1 使用集合创建的RDD 的分区数

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    // 创建RDD
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6))
    // 手动设置分区数
    // val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6) . 6)
    // 查看RDD的分区数  , 在不指定分区数的情况下,RDD使用默认分区数
    /**
     * 1 加载使用集合创建RDD local模式下默认的分区数是 当前环境的可用核数
     *   1) setMaster("local[*]") 本地的所有核数
     *   2) setMaster("local[8]")  分配的8个核数
     *   3) conf.set("spark.default.parallelism", 4) 参数设置
     */
    println(rdd.partitions.size)
    sc.stop()
  }

2 加载文件创建RDD

 def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    // 1 相对路劲读取本项目下的文件创建RDD
    val rdd1: RDD[String] = sc.textFile("spark-core/data/a.txt")
    // 2 读取HDFS分布式文件系统中的文件创建RDD  1K  70M  212M  4
    val rdd3: RDD[String] = sc.textFile("hdfs://doit01:8020/wc/input/")
    /**
     * 根据文件数据创建RDD分区数至少为2
     * SplitSize 的计算逻辑
     * -- FileInputFormat.getSplits(JobConf job, int numSplits)
     * -- val goalSize: Long = totalSize / (if (numSplits == 0)  { 1}
     * else  { numSplits}).toLong
     * -- this.computeSplitSize(goalSize, minSize, blockSize);
     * -- protected long computeSplitSize(long goalSize, long minSize, long blockSize) {
     * return Math.max(minSize, Math.min(goalSize, blockSize));(1,3)}
     */
    println(rdd3.partitions.size)
    sc.stop()
  }

3 分数数据如何划分

集合RDD

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    val ls = List(1,2,3,4,5)
    //创建RDD  指定分区为3
    val rdd: RDD[Int] = sc.makeRDD(ls, 3)
    //生成3个结果文件 [1]  [2,3]   [4,5]
    rdd.saveAsTextFile("data/output1")
    sc.stop()
  }
---------------------------------------源码解析--------------------------
1) val rdd: RDD[Int] = sc.makeRDD(ls, 3)
2) makeRDD(ls, 3)--> 
     def makeRDD[T: ClassTag](
      seq: Seq[T], // data
      numSlices: Int = defaultParallelism): RDD[T] = withScope {  //3
    parallelize(seq, numSlices) // (data,3)
  } 
3) parallelize(seq,numSlices) -->
         def parallelize[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    assertNotStopped()
    new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]()) // 看这里两个参数
  }
4)  new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())-->
       override def getPartitions: Array[Partition] = {
    val slices = ParallelCollectionRDD.slice(data, numSlices).toArray   // 看这里 
    slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray
  }
5)slice(data, numSlices)---> 
      case _ =>   // 看这里
        val array = seq.toArray // To prevent O(n^2) operations for List etc
        positions(array.length, numSlices).map { case (start, end) =>  // 查看方法
            array.slice(start, end).toSeq
        }.toSeq
6)   positions(array.length, numSlices)--->
      def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {   // 数据核心分配逻辑
      (0 until numSlices).iterator.map { i => [0,3)  length=seq.length=5
        val start = ((i * length) / numSlices).toInt
        val end = (((i + 1) * length) / numSlices).toInt
        (start, end)
      }
(0,1)
(1,3)
(3,5)
7) array.slice---->
override def slice(from : scala.Int, until : scala.Int) : scala.Array[T] = { /* compiled code */ }

加载文件创建RDD

读取文件数据时,数据是按照Hadoop文件读取的规则进行切片分区,而切片规则和数据读取的规则不一样

4 如何修改RDD的分区

 /*
     * 在创建RDD的时候 参数二决定RDD的个数 
     */
    sc.makeRDD(List(1,2,3,4,5,6,7),3)
    sc.textFile("d://word.txt",3)

reparation函数          

/*
     * 在创建RDD的时候 参数二决定RDD的个数
     */
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7), 3)
    println(rdd1.partitions.size)
    // 使用repartition方法可以改变RDD的分区 , 返回新的RDD 
    // 一般情况下 ,改变RDD的分区,数据会产生shuffle 
    val rdd2: RDD[Int] = rdd1.repartition(2)
    val rdd3: RDD[Int] = rdd1.repartition(4)
    println(rdd2.partitions.size) // 2
    println(rdd3.partitions.size) // 4

coalesce函数

def main(args: Array[String]): Unit = {
    val sc: SparkContext = SparkUtil.getSc
    /*
     * 在创建RDD的时候 参数二决定RDD的个数
     */
    val rdd1: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5, 6, 7), 3)
    println(rdd1.partitions.size)
    // 较少分区 
    val rdd2: RDD[Int] = rdd1.coalesce(2)
    // 增加分区  发现没有成功
    val rdd3: RDD[Int] = rdd1.coalesce(4)
    println(rdd2.partitions.size) // 2
    println(rdd3.partitions.size) // 3
    sc.stop()
  }

修改默认并行度

 def main(args: Array[String]): Unit = {
    val conf: SparkConf = new SparkConf()
      .setMaster("local[*]") 
      .setAppName(this.getClass.getSimpleName)
      // 处理数据的并行度   16核  16个并行
      conf.set("spark.default.parallelism" , "32") // 并发  32task    16C[16task]
      val sc = new SparkContext(conf)
    val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4))
    println(rdd1.partitions.size) //12
  }

1)、task数量,至少设置成与Spark application的总cpu core数量相同(最理想情况,比如总共150个cpu core,分配了150个task,一起运行,差不多同一时间运行完毕)。

2)、官方是推荐,task数量,设置成spark application总cpu core数量的2~3倍,比如150个cpu core,基本要设置task数量为300~500。

实际情况,与理想情况不同的,有些task会运行的快一点,比如50s就完了,有些task,可能会慢一点,要1分半才运行完,所以如果你的task数量,刚好设置的跟cpu core数量相同,可能还是会导致资源的浪费。比如150个task,10个先运行完了,剩余140个还在运行,但是这个时候,有10个cpu core就空闲出来了,就导致了浪费。那如果task数量设置成cpu core总数的2~3倍,那么一个task运行完了以后,另一个task马上可以补上来,就尽量让cpu core不要空闲,同时也是尽量提升spark作业运行的效率和速度,提升性能。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值