一、逻辑处理
spark将输入输出、中间数据抽象表示为统一的数据模型(数据结构),命名为RDD。RDD是一个逻辑概念,包含多个数据分区。
RDD上定义了两类操作,transformation和action。后者产生输出结果,触发spark提交job真正执行数据处理任务。前者是单向操作。
窄依赖:子RDD每个分区都依赖父RDD中的一部分分区。
宽依赖:输出一个shuffle stage。
如果父RDD的分区整个被子RDD依赖,则是窄依赖。如果父RDD的一些分区被拆开,这个分区只有一部分数据被拉取到子RDD的某个分区,则是宽依赖。窄依赖可以pipelined execution。
二、 物理执行计划
1.根据action操作顺序将应用划分为job
2.根据shuffleDependency将job划分为stage(对每个job,从最后一个RDD往前回溯,遇到窄依赖就合并,遇到宽依赖建立一个stage)
3.根据分区计算将各个stage划分为task(task分为ShuffleMapTasks和ResultTasks)
三、shuffle
聚合、排序、join等操作需要重新分区。shuffle write是map端每个分区写一个shuffle文件,shuffle read是reduce端每个分区从map端的各个分区拉取所需数据。
shuffle write:写shuffle文件方法writePartitionedFile。每个分区生成一个shuffle文件供reduce端拉取。这个shuffle文件按照reduce端的partition id排序或者作为索引。
SortShuffleWriter
创建一个ExternalSorter
如果map端聚合,则使用map--PartitionedAppendOnlyMap
如果不聚合,则使用buffer--PartitionedPairBuffer
这两个结构都具有按键排序的功能(destructiveSortedWritablePartitionedIterator)
对于reduceByKey、sql group by这样的操作,就是每个分区创建一个AppendOnlyMap,在map端聚合,然后写shuffle文件,写之前AppendOnlyMap按照下游的partition id排序,供reduce端读取。
对于row_number、sortByKey,则使用PairBuffer。查看源码可知,ExternalSorter传入的ordering参数传入的是None,实际上它们在map端也是按partition id排序,并没有按key排序。
BypassMergeSortWriter
map端不聚合且分区数少于200开启,例如groupByKey(100),partitionBy(100),
sortByKey(100)。reduce端有200个分区,则map端每个task都打开200个buffer(默认32k)。大量小缓存可能造成较大开销。写shuffle文件时仍然合并成一个,缓解小文件问题。
四、缓存和checkpoint的区别
1.缓存是为了加速计算,checkpoint是为了任务失败时能快速恢复。
2.缓存主要用内存,checkpoint主要用分布式文件系统。
3.checkpoint写入速度慢,为了减少对当前job的时延影响,会额外启动专门的job进行持久化。
4.checkpoint的RDD的lineage会被切断。
5.缓存适用于多次读取、不太大的RDD,checkpoint适用于计算链非常长的RDD。
五、内存管理
内存消耗:用户代码、shuffle中间数据、缓存数据
统一内存管理模型
堆内内存+堆外内存
系统保留空间(300MB)+用户代码空间+框架执行空间(shuffle)+数据缓存空间
spark.executor.memory=1GB
用户代码空间+框架执行空间(shuffle)+数据缓存空间=1GB-300MB
spark.memory.fraction=0.6
框架执行空间(shuffle)+数据缓存空间=(1GB-300MB)* 0.6
spark.memory.storageFraction=0.5
数据缓存空间=(1GB-300MB)* 0.6 * 0.5