spark-3.0.1源码阅读之文件数据计算的补充内容
之前对计算部分的主要代码进行了分析,但是一些重要的细节没有展开来说,本人思量了一下,在这里对其给出补充.
这里主要以hadoop为例,说明一下spark的分片处理和shuffle处理,也是参照hadoop的部分,给出对比和解释.
1 数据读入时的分片处理
由于spark只是作为计算引擎,本身不包含数据的存储功能,所以关于输入数据的分片要么使用数据源的分片功能,要么进行简单的对数据进行划分,这部分内容不会很复杂.
在之前的文件数据计算的源码阅读中,在val value = sc.textFile(outputDir)这一行并没有打上断点,因为这里所说的hadoop的分片处理并没有使用spark的代码,而是使用hadoop自身的,这里进去可以看到
textFile方法使用了默认的分片数,同时hadoopFile的输入参数为TextInputFormat,这个是mapred里的类.
1.1 defaultMinPattions分析
查看其调用的方法树
可见,最终是CoarseGrainedSchedulerBackend的defaultparallelism方法,
最终的取值为配置中的spark.default.parallelism的值,如果没有设置,则取总共的core的数目.
1.2 分片数分析
hadoopFile方法中的核心是创建HadoopRDD,该RDD复写了getPartitions方法,此方法就是获取分片数,也就是spark的分区数.其调用的方法树为
可见,调用的是传入的参数TextInputFormat的父类的getSplits方法
可以看到,根据输入的numSplits,也就是之前的defaultMinPattions的值,先计算了目标分片的大小,同时计算了minSize,接着使用了computeSplitSize方法
此方法也很简单,就是取最大值而已.
先获取目标分片大小和blockSize的最小值,再和前面计算出的minSizse比较,取最大值即可,得到的就是实际分片的大小,使用文件的总大小除以分片的实际大小就是分片数,也就是最终在spark里的分区数.
比如这里设置了spark.default.parallelism的值为20,总文件大小为2000M,那么2000M/20=100M,就小于128M(blockSize),取其较小者100M为实际分片大小,2000M/100M=20,即分区数为20.可见,当spark.default.parallelism的值设置的比较大的时候,分区数就会和改值相同.
2 令人迷惑的shuffle
之所以说这个shuffle总是令人迷惑,因为其目的和过程与shuffle这个名字有些不匹配,或者说容易使人产生疑惑.
Shuffle的本意是洗牌的意思,洗牌无非是把牌打散使其分布均匀,如果是mapred或spark真的按照shuffle的本意使得要计算的数据被打散分布均匀,那么这一切都失去了意义,因为本来就是要reduce或聚合的,当然是相似的数据越聚集越好,而不是越分散越好.
2.1 mr和spark的计算目的
Mr的计算目的很明确,第一步就是映射出形如<key,value>的键值对,第二步就是把第一步的键值对做合并,相同的键合起来就可以了.
相比之下,spark的计算就没有这么清晰的目的,可以仅仅是map计算,也可以是先map计算,再reduce计算,所以没有mr那么严格的计算流程,但是最复杂的计算依然是聚合,也就是mr中的reduce.
因此,Mr和spark的计算目的本质上是相同的,最终都是为了要进行聚合计算.
2.2 shuffle之于mr和spark
由于mr和spark的计算目的相同,所以shuffle对于两者的作用本质上也是一样的.
Mr使用shuffle的目的是在reduce之前就形成<key,Iterator>的形式,这样reduce只需要执行合并的逻辑即可,而spark使用shuffle的目的也是类似的,使得最后的聚合可以直接使用聚合逻辑,而不用考虑之前的一些列操作,比如排序分区等.
可见,mr和spark的计算其实具体分为三阶段,首先是map,其次是shuffle,最后是reduce,那么shuffle的作用就清楚了.
2.3 mr的shuffle的作用
如前所述,作为计算的第二阶段,shuffle的作用很明显,那就是执行一系列的计算逻辑使<key,value>的集合变为<key,Iterator>的集合.
mr的shuffle主要操作有:
1 map端的shuffle
Map端的shuffle的操作对象是每个map任务的结果,主要操作有分区,排序,溢写磁盘,合并磁盘分区.
分区是根据key把map结果再次分类,每个reduce任务对应一个分区,借助环形缓冲区,把数据先排序,在此过程中把相同分区的数据组织在一起,再写入磁盘,在磁盘上进行分区的合并,最终形成一个文件,该文件内包含各个分区,分区内的数据有序.
可见,每个map任务最重经过shuffle的处理,形成唯一一个最终包含多分区的文件.
2 reduce端的shuffle
Reduce端的shuffle操作的对象就很明显了,那就是各个map任务端的shuffle最终形成的包含多个分区的文件.主要操作有拉取,排序,归并分组,形成最终的<key,Iterator>形态.
Shuffle把每个map的最终合并文件拉取过来,不必等到所有的map端的shuffle结束,只要文件准备就绪,就可以拉取.拉取过来后进行排序,对不同map端的文件内同一分区的数据重新再排序,最后对相同的key进行分组,形成<key,Iterator>,供reduce使用.
2.4 spark的shuffle的作用
Spark的shuffle最初并不像hadoop那样,是按照分区来进行最终的聚合的.之后由于效率的问题,spark也参照了hadoop的shuffle模式,采用了分区的模式,不同的是没有使用类似环形缓冲区生成溢写文件,之后再合并为一个最终文件的过程,而是每个map任务直接在文件中顺序写分区数据,同时生成一个索引文件来记录每个分区的大小和位置.
最终,spark的shuffle几乎和hadoop的shuffle一致了,都是基于分区来进行聚合,唯一不同的是期间多生成了一个索引文件.
3 总结
Spark在读取hdfs文件时,采用了hadoop的mapred的类来进行分区,期间的数据计算中使用shuffle,和hadoop的shuffle几乎一模一样,总结起来就是
1 分区对于spark来说至关重要,这直接与shuffle有关,同时也与资源的利用率有关
2 hadoop的shuffle和spark的shuffle基本一致,两者的处理逻辑完全相同,只是过程少有不同而已.
3 shuffle中的耗时操作明显是拉取文件,hadoop以每次拉取一个为单位,spark以每次拉取两个为单位.
4 mr的计算健壮性明显更强,只使用了一个环形缓冲区,没有过多的涉及内存计算,而spark则容易出现内存方面的问题,比如oom
如果不是很在意时间,使用mr绝对是第一选择.