Spark- 之不同Source产生RDD的分区数与数据分配

本文深入探讨了Spark中内存集合与文件作为数据源时RDD的分区数确定与数据分配机制。对于内存集合,RDD分区数由`numSlices`决定,可手动设置或默认为`defaultParallelism`,数据分配通过`parallelize()`实现,根据元素下标范围进行切分。而对于文件数据源,分区数通常与`minPartitions`相关,若未指定则默认为`defaultMinPartitions`,实际分区数可能因文件大小与行数而增加。数据分配遵循Hadoop的行读取方式,按文件偏移量进行分配。

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

Spark- 之不同Source产生RDD的分区数与数据分配

通常Spark的数据源可以分为很多中,这里主要是从源码剖析内存集合文件分区数的确定与数据分配。

1 集合RDD的分区与数据分配

具体看以下代码及注释。

package com.shufang.parallel_yuanli

import com.shufang.utils.ScUtil
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
 */
object RddFromMemoryCollection {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[8]").setAppName("parellel")
      .set("spark.default.parallelism", "5")

    val sc: SparkContext = new SparkContext(conf)


    /**
     * TODO makeRDD() 底层调用的就是 parallelize()
     * TODO 1 :如何确定分区的数量
     * 通常numSlices代表RDD的分区的个数,那么这个分区的个数呢可以手动指定,也可以使用默认值
     * 当手动指定时,RDD的分区个数:numSlices
     * 如果使用默认值,RDD的分区个数:numSlices => numSlices = defaultParallelism = defaultParallelism()
     * def defaultParallelism: Int = {
     * assertNotStopped()
     * taskScheduler.defaultParallelism
     * }
     *
     * taskScheduler.defaultParallelism =
     * override def defaultParallelism(): Int =
     * backend.defaultParallelism()
     *
     * override def defaultParallelism(): Int =
     * scheduler.conf.getInt("spark.default.parallelism", totalCores)
     * totalCores是当前环境master能够使用的最大核数,比如totalCores = local[*]或者
     * set spark.executor.cores = 5;
     * set executor.nums = 3;
     * =>>>>>> totalCores = 15
     * ==================================================================================
     * TODO 2 :如何给数据找准对应的分区进行分配
     * def parallelize[T: ClassTag](
     * seq: Seq[T],
     * numSlices: Int = defaultParallelism): RDD[T] = withScope {
     * assertNotStopped()
     * TODO 2.1 这是主要的数据分区分配的入口
     * new ParallelCollectionRDD[T](this, seq, numSlices, Map[Int, Seq[String]]())
     * }
     * TODO 2.2 找到每个分区的元素的下标范围:[start,end)
     * def positions(length: Long, numSlices: Int): Iterator[(Int, Int)] = {
     * (0 until numSlices).iterator.map { i =>
     * val start = ((i * length) / numSlices).toInt
     * val end = (((i + 1) * length) / numSlices).toInt
     * (start, end)
     * }
     * }
     * TODO 2.3 seq是传入的集合,然后将范围内的元素进行切分给不同的分区
     * 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
     * TODO 当前有 5 个元素,分区数为2
     * 分区0  [0*5/2,1*5/2) => [0,2)  => (1,2)
     * 分区1  [1*5/2,2*5/2) => [2,5)  => (3,4,5)
     * 所以最终输出产生2个文件:
     * part-00000
     * 1
     * 2
     * part-00001
     * 3
     * 4
     * 5
     *
     */
    val rdd: RDD[Int] = sc.makeRDD(List(1, 2, 3, 4, 5), 2)

    rdd.saveAsTextFile("output")

    sc.stop()
  }
}

2 文件RDD的分区与数据分配

package com.shufang.parallel_yuanli

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

object RddFromFile {
  def main(args: Array[String]): Unit = {

    val conf: SparkConf = new SparkConf().setMaster("local[8]").setAppName("parellel")
      .set("spark.default.parallelism", "5")

    val sc: SparkContext = new SparkContext(conf)


    /**
     * TODO 1 分区数确定:文件的分区数与minPartition这个参数有关,通常可以通过textFile指定最小分区数,但这个并不是最终分区数量!!
     * TODO 1.1 如果没有指定,那么minPartitions = defaultMinPartitions = math.min(defaultParallelism,2)
     * def textFile(
     * path: String,
     * minPartitions: Int = defaultMinPartitions): RDD[String] = withScope {
     * assertNotStopped()
     * hadoopFile(path, classOf[TextInputFormat], classOf[LongWritable], classOf[Text],
     * minPartitions).map(pair => pair._2.toString).setName(path)
     * }
     * TODO 1.2 很显然 Spark读取数据按照Hadoop TextInputFormat的方式进行读取,所以是按照行读取,分区计算方式如下:
     * extends FileInputFormat<LongWritable, Text>,进入FileInputFormat的 getSplits()获取切片的方法
     *  - totalSize : 文件的总字节大小  <= totalSize = file.getLength();
     *  - goalSize = totalSize / (long)(numSplits == 0 ? 1 : numSplits); numSplits就是minPartitions
     * TODO 1.3 goalSize就是最终的分区存储的字节数量(如果能整除),假如 totalSize = 7 ,minPartitions(numSplits) = 2
     * => goalSize = 7 / 2 =  3 ... 1 ,现在每个分区的字节数为3,余数为1,按照Hadoop的分区规则,1+3/3 > 1.1
     * => 最终RDD的分区个数为:2 + 1 = 3
     *
     * TODO 1.4 files/wordcount.txt为75个字节,包括换行符等,此时使用默认的defaultMinPartitions = 2
     * => totalSize = 75
     * => goalSize = 75/2 = 37...1  1/37 < 0.1 所以最终的分区个数为2
     * =======================================================================================================
     * TODO 2 数据的分区分配
     * TODO 2.1 数据按照行读取,偏移量不会重复读取
     * TODO 2.2 数据分配按照偏移量分配
     * 分区  偏移量    最终读取的行号,第一行22字节,第二行22字节,第三行31字节
     * 分区0 [0,37]   N1、N2
     * 分区1 [38,75]  N3
     * TODO 所以最终的数据第1,2行被分配到第一个分区文件
     * part-00000
     * spark   spark   hello
     * flink   world   hello
     * TODO 第三行被分配到第二个分区文件
     * part-00001
     * good man    good    good good
     *
     */
    val rdd: RDD[String] = sc.textFile("files/wordcount.txt")

    rdd.saveAsTextFile("output")

    sc.stop()
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值