文章目录
1.先码一个demo
object RDD_Memory_Par {
def main(args: Array[String]): Unit = {
// TODO 准备环境
val sparkConf = new SparkConf()
.setMaster("local[*]")
.setAppName("RDD_Memory_Par")
.set("spark.default.parallelism","5")
val sc = new SparkContext(sparkConf)
// TODO 创建RDD
// 考虑RDD的并行度 & 分区
val rdd = sc.makeRDD(List(1, 2, 3, 4,5),3)
// 将处理的数据保存成分区文件
rdd.saveAsTextFile("scala01/data/output01")
// TODO 关闭环境
sc.stop()
}
}
2.看结果
生成三个文件,文件数据分别为(1)、(2, 3)、(4, 5)
3.看源码
scala看源码一定要注意找到你要看的对象,再找到你最后一步要看的方法
我们现在要剖析的是为什么会造成RDD的数据集这样分配的,那我们先看RDD如何生成的。
val rdd = sc.makeRDD(List(1, 2, 3, 4,5),3)
无疑是这一句,那我就进入makeRDD的源码剖析。
点击进入后,会发现这一段源码。
def makeRDD[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = withScope { parallelize(seq, numSlices) }
很明显,这里没有你要的计算方法,那我们就跟随对象,继续探索。
点击parallelize查看源码
def parallelize[T: ClassTag]( seq: Seq[T], numSlices: Int = defaultParallelism): RDD[T] = withScope { assertNotStopped() new ParallelCollectionRDD[T]( this, seq, numSlices, Map[Int, Seq[String]]() ) }
看不见分配方法,继续点击对象。
点击ParallelCollectionRDD查看源码
进入之后,下滑,看见了我们想看的分区方法
override def getPartitions: Array[Partition] = { val slices = ParallelCollectionRDD.slice(data, numSlices).toArray slices.indices.map(i => new ParallelCollectionPartition(id, i, slices(i))).toArray}
到这一步会看见这里通过ParallelCollectionRDD定义了slices的常量,后面又进行了map的切分,好像感觉快到了,但是数据的分配如何计算的我们还是没有看见,那愣着干嘛,继续深入。
点击ParallelCollectionRDD查看源码。
点击进入之后发现是一个private Object,是一个执行对象,感觉计算方法应该在这个里面。
下滑没发现了一长串的模式匹配。
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 }
这部分就是对我们数据集的匹配,明显前面两个range匹配与我们不符,查看最后一个其他匹配,在这里面拿到了数据集的长度 val array = seq.toArray, array.length以及分区数numSlices,通过positions函数进行计算。
那我点击positions看看它是怎么被计算的
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) } }
点击进入之后终于发现了我们想要分配计算方法
0 until numSlices:0-分区数,在我们的demo里就是0-3,until为开区间。
val start = ((i * length) / numSlices).toInt:i为第几个分区,length为数据集数据长度,numSlices为分区数,计算得出的start即这个i应该存放的第一个数据索引,以第一个分区为例:我们这里就是第一个分区存放的第一个索引为0,也就是第一个数据1。
val end = (((i + 1) * length) / numSlices).toInt:同理,这里就确定了一个分区里最后一个索引为何,同样以第一个分区为例:(0+1)*5/3 即得到的为1。
所以第一个分区存放的索引为 [0,1),转换为数据即1。
其他分区以此同理。
计算得出:
0分区:1
1分区:2,3
2分区:4,5
没有看懂的小伙伴,如果想了解,给我联系方式,手把手帮助哦!
帮忙点个关注吧^.^