每一个shuffle的前半部分stage的task,每个task都会创建下一个stage的task数量相同的文件,比如下一个stage会有100个task,那么当前stage每个task都会创建100份文件,会将同一个key对应的values,一定是写入同一个文件中的,也一定会将同一个key对应的values写入下一个stage,同一个task对应的文件中。
shuffle的后半部分stage的task,每个task都会从各个节点上的task写的属于自己的那一份文件中,拉取key,value对;然后task会有一个内存缓存区,然后用hashMap然后进行key-values进行聚合(key,values);
task 会用我们自己定义的聚合函数,进行聚合
shuffle,一定是分为俩个stage来完成的,因为这其实是个逆向的过程,不是stage决定shuffle,是shuffle决定stage
reduceByKey(_ + _) 在某个action触发job的时候,DAGScheduler,会负责划分job为多个stage.,划分的依据,就是发现有会触发shuffle操作的算子,比如reduceByKey,就将这个操作的前半部分,以及以前所有的RDD和transformation操作,划分为一个stage,
优化一:合并map端输出文件
new sparkConf().set("spark.shuffle.consolidateFiles","true")
开启shufflemap端输出文件合并的机制,默认是不开启的,就会发生下边大量map端输出文件的操作,消耗大量的性能
如果不合并map端输出文件的话,会怎么样?
问题来了,默认的这种shuffle行为,对性能有什么样的恶略影响呢?
实际生产环境的条件:
100个节点,每个节点100个executor,:100个executor
每个executor:2个cpu core
总共1000个task,每个executor平均10个task 每个task输出下个stage的task数量文件
每个节点,10个task,会输出多少分map端文件 10 * 1000 = 1万个文件
总共有多少份map端输出文件?100 * 10000 = 100 万
shuffle中的写磁盘的操作,基本上就是shuffle中性能消耗最为严重的部分。
通过上面的分析,一个普通的生产环境的spark job的一个shuffle环节,会写入磁盘100万个文件
磁盘IO对性能和spark作业执行速度的影响,是及其惊人和吓人的。
基本上,spark作业的性能,都消耗在shuffle中了,虽然不只是shuffle的map端输出文件这一个部分,但是这里也是非常大的一个性能消耗点
开启map端输出文件的合并机制后
第一个stage,同时就运行cpu core个task,比如cpu core是2个,那么就并行运行2个task;每个task都创建下一个stage的task数量个文件;
第一个stage,并行运行的俩个task,执行完以后就会执行另外俩个task,另外2个task不会再创建输出文件;而是复用之前的task创建的map端输出文件,将数据写入上一批task的输出文件中。
第二个stage,task在拉取数据的时候,就不会去拉取上一个stage每个task为自己创建的那份输出文件了,而是拉取少量的输出文件,每个输出文件中,可能包含了多个task给自己的map端输出。
提醒一下(map端输出文件合并)
只有并行执行的task回去创建新的输出文件;下一批 并行执行的task,就会去复用之前已有的输出文件,但是有一个例外,比如2个task并行在执行,此时又启动要执行2个task,就无法去复用刚才那俩个task创建的输出文件了,而是自己去创建新的输出文件
要实现输出文件合并的效果,必须是一批task先执行,然后下一批task再执行,才能复用之前的输出文件,否则多批task同时起来执行,还是做不到复用。
合并map端输出文件,对咱们的spark的性能有哪些方面的影响呢?
1. map task写入磁盘文件的IO,减少:100万 --> 20 万
2. 第二个stage,原来要拉取第一个stage的task数量份文件,1000个task,第二stage都要拉取1000份文件,要走网络传输