Spark RDD 并行度与分区策略剖析 | 源码探索

目录

读取内存时数据并行度与分区算法

 概念解释

 读取内存时数据并行度与分区算法

读取内存数据并行度算法

 读取内存数据分区算法

读取文件时数据分区算法

小结

如果觉得本文对你有帮助,想进一步学习SQL语言这门艺术的,那么不妨也可以选择去看看我的博客专栏 ,部分内容如下:

数字化建设通关指南

专栏 原价99,现在活动价59.9,按照阶梯式增长,直到恢复原价。


读取内存时数据并行度与分区算法

 概念解释


默认情况下,Spark可以将一个作业切分多个任务后,发送给Executor节点并行计算,而分区数我们称之为并行度,并行度等于task总数,但task数并不等于某一时刻可以同时并行计算的任务数。这个数量可以在构建RDD时指定。

 读取内存时数据并行度与分区算法


读取内存数据并行度算法

makeRDD的源码

  def makeRDD[T: ClassTag](
      seq: Seq[T],
      numSlices: Int = defaultParallelism): RDD[T] = withScope {
    parallelize(seq, numSlices)
  }


从makeRDD的源码可以看出makeRDD底层调用的是parallelize(seq, numSlices),也就是说makeRDD是对parallelize的封装。从源码中可以知道makeRDD需要传入两个参数,一个是从内存中创建的数据源(序列),一个为numSlices,需要你传入的并行度(分片数),默认为numSlices: Int = defaultParallelism(默认并行度)。

默认RDD的分区数就是并行度,设置并行度就是设置分区数。通过makeRDD的第二个参数就可以修改并行度,那么默认并行度是多少呢?数据分区的核心规则是什么呢?

点进去查看源码:

(1)在makeRDD位置处点击进入查看源码

进入后代码如下:

(2)继续进入查看

 

发现为抽象方法,抽象方法必有实现类:idea查看实现类的方法 ,crtl + alt + b

(3) 查找结果如下

 (5)最终找到源码如下

tips:idea中查找实现类的方法crtl + alt + b.找到后ctrl + f 查找该方法

最终核心源码如下:

 override def defaultParallelism(): Int =
    scheduler.conf.getInt("spark.default.parallelism", totalCores)


由源码可以看出如果获取不到指定的参数("spark.default.parallelism")会采用默认值totalCores,totalCores为当前环境下可用的机器总核数。 什么 意思呢?该核数是由下面代码设置的参数决定的

val conf = new SparkConf().setAppName("word count").setMaster("local") 


具体之间关系如下图所示:

其中 setMaster()中配置的参数为当前环境下可用的核数。当前环境包括windows环境及linux环境等等

  • 当为loacal的时候表示只有一个核
  • 当为local[*]的时候表示机器中所有的核数
  • 当为local[3]表示使用3个核数,可以由该参数指定。

具体示例代码如下

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
 
object testMakeRDD {
 
    def main(args: Array[String]): Unit = {
 
        //TODO spark从内存中创建RDD
        //默认RDD分区的数量就是并行度,设定并行度就是设定分区数量,但分区数量不一定就是并行度
        //资源不够的情况下分区数与并行度是不等的
        val conf = new SparkConf().setAppName("word count").setMaster("local[*]") //单核数
        //创建spark上下文
        val sc = new SparkContext(conf)
        //1.makeRDD的第一个参数:数据源
        //2.makeRDD的第二个参数:并行度(分区数量)
        val rdd: RDD[Int] = sc.makeRDD(List(1,2,3,4))
        //不传参数的话默认分区数是多少?
//        println(rdd.collect().mkString(","))
//        将RDD的处理数据保存到文件中
        rdd.saveAsTextFile("output")
        sc.stop()
    }
 
}

代码执行结果如图所示,由于采用的是本地windows环境有八个核,因而生成8个文件

 读取内存数据分区算法

分区算法指的是:数据最终被分配到哪个分区中计算。一个分区生成一个文件

1 测试

object testFenQu {
 
    def main(args: Array[String]): Unit = {
 
        //TODO spark从内存中创建RDD
 
        val conf = new SparkConf().setAppName("word count").setMaster("local[*]")
        //创建spark上下文
        val sc = new SparkContext(conf)
//        val rdd1: RDD[Int] = sc.makeRDD(List(1,2,3,4),2)
//        rdd1.saveAsTextFile("output")
 
//        val rdd2: RDD[Int] = sc.makeRDD(List(1,2,3,4),4)
//        rdd2.saveAsTextFile("output")
 
//        val rdd3: RDD[Int] = sc.makeRDD(List(1,2,3,4),3)
 //       rdd3.saveAsTextFile("output")
 
 
        val rdd4: RDD[Int] = sc.makeRDD(List(1,2,3,4,5),3)
        rdd4.saveAsTextFile("output")
        sc.stop()
    }
 
 
 
}

执行上述代码RDD4为例,其结果如下:

由代码可知有3个分区,则产生3个文件

打开文件part-00000,发现只有1。
打开文件part-00001,发现2,3。
打开文件part-00002,发现4,5。

为什么是这样的呢?

2 查看源码

 (1)点makeRDD查看源码

 (2)点击parallelize(seq, numSlices)进一步查看

(3) 步骤3

(4) 步骤4

最终得到数据分区代码如下:

private object ParallelCollectionRDD {
  /**
   * Slice a collection into numSlices sub-collections. One extra thing we do here is to treat Range
   * collections specially, encoding the slices as other Ranges to minimize memory cost. This makes
   * it efficient to run Spark over RDDs representing large sets of numbers. And if the collection
   * is an inclusive Range, we use inclusive range for the last slice.
   */
  def slice[T: ClassTag](seq: Seq[T], numSlices: Int): Seq[Seq[T]] = {
    if (numSlices < 1) {
      throw new IllegalArgumentException("Positive number of partitions required")
    }
    // Sequences need to be sliced at the same set of index positions for operations
    // like RDD.zip() to behave as expected
    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)
      }
    }
    seq match {
      case r: Range =>
        positions(r.length, numSlices).zipWithIndex.map { case ((start, end), index) =>
          // If the range is inclusive, use inclusive range for the last slice
          if (r.isInclusive && index == numSlices - 1) {
            new Range.Inclusive(r.start + start * r.step, r.end, r.step)
          }
          else {
            new Range(r.start + start * r.step, r.start + end * r.step, r.step)
          }
        }.toSeq.asInstanceOf[Seq[Seq[T]]]
      case nr: NumericRange[_] =>
        // For ranges of Long, Double, BigInteger, etc
        val slices = new ArrayBuffer[Seq[T]](numSlices)
        var r = nr
        for ((start, end) <- positions(nr.length, numSlices)) {
          val sliceSize = end - start
          slices += r.take(sliceSize).asInstanceOf[Seq[T]]
          r = r.drop(sliceSize)
        }
        slices
      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
    }
  }
}

通过分析上述代码可知,对于内存中创建的数据最终走的是case _这段代码,这段代码调用的方法为:

传入的参数为数组的长度及分片数

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) //返回数据的偏移量左闭右开
 
  }
 
}
 
 
-------------------------------------
 
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

 

代码解释如下:

举例:


 注意:until是左闭合右开的

总结:内存中数据能够被整除的时候基本上是平均分配,如果不能被整除则按照一定算法进行分配

读取文件时数据分区算法

读取文件数据时,数据是按照Hadoop文件读取的规则进行切片分区,而切片规则和数据读取的规则有些差异,具体Spark核心源码如下

public InputSplit[] getSplits(JobConf job, int numSplits)
 
    throws IOException {
 
 
    long totalSize = 0;                           // compute total size
 
    for (FileStatus file: files) {                // check we have valid files
 
      if (file.isDirectory()) {
 
        throw new IOException("Not a file: "+ file.getPath());
 
      }
 
      totalSize += file.getLen();
 
    }
 
 
    long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
 
    long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
 
      FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);
 
     
 
    ...
 
   
 
    for (FileStatus file: files) {
 
   
 
        ...
 
   
 
    if (isSplitable(fs, path)) {
 
          long blockSize = file.getBlockSize();
 
          long splitSize = computeSplitSize(goalSize, minSize, blockSize);
 
 
          ...
 
 
  }
 
  protected long computeSplitSize(long goalSize, long minSize,
 
                                       long blockSize) {
 
    return Math.max(minSize, Math.min(goalSize, blockSize));
 
  }
 

分区测试
 

object partition03_file {
 
    def main(args: Array[String]): Unit = {
        val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("SparkCoreTest1")
        val sc: SparkContext = new SparkContext(conf)
 
        //1)默认分区的数量:默认取值为当前核数和2的最小值
        //val rdd: RDD[String] = sc.textFile("input")
        //2)输入数据1-4,每行一个数字;输出:0=>{1、2} 1=>{3} 2=>{4} 3=>{空}
        //val rdd: RDD[String] = sc.textFile("input/3.txt",3)
        //3)输入数据1-4,一共一行;输出:0=>{1234} 1=>{空} 2=>{空} 3=>{空} 
        val rdd: RDD[String] = sc.textFile("input/4.txt",3)
        rdd.saveAsTextFile("output")
        sc.stop()
    }
}

小结

如果觉得本文对你有帮助,想进一步学习SQL语言这门艺术的,那么不妨也可以选择去看看我的博客专栏 ,部分内容如下:
数字化建设通关指南
专栏 原价99,现在活动价59.9,按照阶梯式增长,直到恢复原价。

专栏主要内容:
(1)SQL进阶实战技巧
可以参考如下教程,具体链接如下

SQL很简单,可你却写不好?也许这才是SQL最好的教程

上面链接中的文章及技巧会不定期更新。

 

(2)数仓建模实战技巧和个人心得
       1)新人入职新公司后应如何快速了解业务?

       2)以业务视角看宽表化建设?

       3)  维度建模 or 关系型建模?

       4)业务模型与数据模型有什么区别?业务阶段的模型该如何建设?

       5)业务指标体系该如何建设?指标体系该如何维护?指标平台应如何建设?指标体系                           该由谁来搭建?

       6)如何优雅设计DWS层?DWS层模型好坏该如何评价?

       7)指标发生异常,该如何排查?应从哪些方面入手寻找问题点?

       8) 数据架构的选择,mpp or hadoop?

       9)数仓团队应如何体现自己的业务价值,讲好数据故事?

       10)BI与大数据有什么关系?BI与信息化、数字化之间有什么关系?BI与报表之间的关                          系?

       11)数据部门如何与业务部门沟通,并规划指引业务需求?

文章不限于以上内容,有新的想法也会及时更新到该专栏。

具体专栏链接如下: 

 

​​​​​​数字化建设通关指南_莫叫石榴姐的博客-CSDN博客

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值