目录
如果觉得本文对你有帮助,想进一步学习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进阶实战技巧
可以参考如下教程,具体链接如下
上面链接中的文章及技巧会不定期更新。
(2)数仓建模实战技巧和个人心得
1)新人入职新公司后应如何快速了解业务?
2)以业务视角看宽表化建设?
3) 维度建模 or 关系型建模?
4)业务模型与数据模型有什么区别?业务阶段的模型该如何建设?
5)业务指标体系该如何建设?指标体系该如何维护?指标平台应如何建设?指标体系 该由谁来搭建?
6)如何优雅设计DWS层?DWS层模型好坏该如何评价?
7)指标发生异常,该如何排查?应从哪些方面入手寻找问题点?
8) 数据架构的选择,mpp or hadoop?
9)数仓团队应如何体现自己的业务价值,讲好数据故事?
10)BI与大数据有什么关系?BI与信息化、数字化之间有什么关系?BI与报表之间的关 系?
11)数据部门如何与业务部门沟通,并规划指引业务需求?
文章不限于以上内容,有新的想法也会及时更新到该专栏。
具体专栏链接如下:
数字化建设通关指南_莫叫石榴姐的博客-CSDN博客