map函数处理完成之后,进入collect阶段,对处理后的k/v对进行收集,存储在内存的环形缓冲区中。
当环形缓冲区中的数据达到阀值之后(也可能一直没有达到阀值,也一样要将内存中的数据写入磁盘),将内存缓冲区中的数据通过SpillThread线程转移到磁盘上。需要注意的是,转移之前,首先利用快排对记录数据进行排序(原则是先按照分区编号,再按照key进行排序,注意,排序是在写入磁盘之前的)。之后按照partition编号,获取上述排序之后的数据并将其写入Spill.out文件中(一个Spill.out文件中可能会有多个分区的数据--因为一次map操作会有多次的spill的过程),需要注意的是,如果人为设置了combiner,在写入文件之前,需要对每个分区中的数据进行聚集操作。该文件同时又对应SpillRecord结构(Spill.out文件索引)。
map的最后一个阶段是merge:该过程会将每一个Spill.out文件合并成为一个大文件(该文件也有对应的索引文件),合并的过程很简单,就是将多个Spill.out文件的在同一个partition的数据进行合并。(第一次聚合)
shuffle阶段。首先要说明的是shuffle阶段有两种阀值设置。第一,获取来自map的结果数据的时候,根据数据大小(file.out的大小)自然划分到内存或者是磁盘(这种阀值的设置跟map阶段完全不同);第二,内存和磁盘能够保存的文件数目有阀值,超出阀值,会对文件进行merge操作,即小文件合并成为大文件。Shuffle过程:
Spark与Hadoop的Shuffle之间第二个明显的差别是,Spark的Shuffle是hash-based类型的,而Hadoop的Shuffle是sort-based类型的。下面简介一下Spark的Shuffle:
1.正因为是算子驱动的,Spark的Shuffle主要是两个阶段:Shuffle Write和Shuffle Read。
2.ShuffleMapTask的整个的执行过程就是Shuffle Write阶段
3.Sprk的Shuffle过程刚开始的操作就是将map的结果文件中的数据记录送到对应的bucket里面(缓冲区),分到哪一个bucket根据key来决定(该过程是hash的过程,每一个bucket都对应最终的reducer,也就是说在hash-based下,数据会自动划分到对应reducer的bucket里面)。之后,每个bucket里面的数据会不断被写到本地磁盘上,形成一个ShuffleBlockFile,或者简称FileSegment。上述就是整个ShuffleMapTask过程。之后,reducer会去fetch属于自己的FileSegment,进入shuffle read阶段。
4.需要注意的是reducer进行数据的fetch操作是等到所有的ShuffleMapTask执行完才开始进行的,因为所有的ShuffleMapTask可能不在同一个stage里面,而stage执行后提交是要在父stage执行提交之后才能进行的,所以fetch操作并不是FileSegment产生就执行的。
5.需要注意的是,刚fetch来的FileSegment存放在softBuffer缓冲区,Spark规定这个缓冲界限不能超过spark.reducer.maxMbInFlight,这里用softBuffer表示,默认大小48MB。
6.经过reduce处理后的数据放在内存+磁盘上(采用相关策略进行spill)。
7.fetch一旦开始,就会边fetch边处理(reduce)。MapReduce shuffle阶段就是边fetch边使用combine()进行处理,但是combine()处理的是部分数据。MapReduce不能做到边fetch边reduce处理,因为MapReduce为了让进入reduce()的records有序,必须等到全部数据都shuffle-sort后再开始reduce()。然而,Spark不要求shuffle后的数据全局有序,因此没必要等到全部数据shuffle完成后再处理。为了实现边shuffle边处理,而且流入的records是无序的可以用aggregate的数据结构,比如HashMap。
hash-based 和 sort-based的对比Hadoop中的Shuffle 与 Spark中的Shuffle得区别与联系
原文:http://blog.csdn.net/u012965373/article/details/51427105