Shuffle原理
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.Shuffle原理
(1)map方法在将数据(K,V,P)写入到环形缓冲区之前就已经获取到分区编号了,只不过此时数据没有落地(溢写到磁盘);
(2)当环形缓冲区数据快满时会发生数据溢写到磁盘,在溢写到磁盘之前会先根据已有分区编号的数据进行排序(如果没有自定义排序算法默认采用HashCode算法),这样做的好处就是各个分区的数据是有序的;
(3)在数据快满时,各个分区排序完成后溢写到磁盘之前,虽然数据已经是有序的,但并不排除有序的数据存在相同key的情况,此时我们可以自定义Combiner将相同的key进行合并后在写入到磁盘,这样可以节省本地磁盘的存储空间;
(4)综上所述,当多次发生数据溢写时(或者所有的数据已经处理完毕)会进行归并排序,即将多个小文件合并成一个较大的有序文件,与此同时,在合并的过程中如果我们自定义了Combiner的话还是会将相同的key进行合并后写入到大文件中以节省磁盘空间,不仅如此,在后期数据传输也可以节省网络带宽;
(5)当各个分区数据归并排序完成之后,假设我们已经使用combiner你觉得带宽的节省不够明显时可以采用压缩的手段,对个分区数据进行压缩,这样在数据量比较大的情况下可以明显看出节省带宽比率,当然如果你这样做的话会消耗CPU资源;
(6)在将溢写的数据归并排序后,通过combiner和压缩手段处理后会再次将数据写入到本地磁盘等待ReduceTask来拉取MapTask处理的数据文件(该文件的特点就是分区且区内有序);
(7)每个ReduceTask会去每个MapTask拉取各个分区的数据并在各个ReduceTask节点上内存缓冲相应的数据,当缓冲区不足时依旧会溢写到磁盘;
(8)当各个ReducTask节点的数据多次溢写到磁盘后,会对每MapTask来的数据进行归并排序生成一个较大的文件(该文件特定是按照key有序,即按照咱们自定义的规则排好序啦);
(9)紧接着就按照相同的key进行分组(我们可以自定义分组算法,但这要求我们在MapTask时对排序规则粒度应该更细);
(10)将分组后的数据传给Reduce方法进行处理;
二.环形缓冲区
我们知道物理内存是条形的,我们这里说的环形缓冲区是逻辑上的定义;
环形缓冲区的特点就是无头无尾。条形缓冲区是有头有尾的;
数据(K,V)在写入环形缓冲区时会有对应的索引,索引的好处就是可以方便快速定位一个数据类型的存储位置,而无需遍历整个环形缓冲区内存;
环形缓冲区默认存储大小是100MB,但数据在未满之前,严格来说是达到80%的时候就开始将环形缓冲区的数据溢写到磁盘,在溢写到磁盘的过程中,还剩余20%的空间可以供给map继续往该缓冲区写入数据;
一般性情况在20%空间还没有写完之前就已经溢写磁盘成功了,这样环形缓冲区就有了更多的空间继续使用,但是当I/O压力比较大的时候,可能会出现环形缓冲区数据已满的现象,此时您可以考虑调大该条形缓冲区的大小,当然你也可以修改当数据达到指定阈值(默认80%)就开始溢写到磁盘;
当环形缓冲区内存使用完时,新的数据是写入不进来的,即程序会陷入阻塞状态,关于环形缓冲区和溢写磁盘时的比例并没有明确的值可以设定,基本上默认可以满足大部分需求,具体情况还得根据实际应用场景做出相应的修改;
我们所说的分区排序的过程其实都是在环形缓冲区中进行的,其大致过程如下所示:
(1)首先,K,V数据写入到环形缓冲区并为其分配一个索引编号,数据写入后,根据K分配相应的分区编号;
(2)排序的过程其实比较的是K类型,但内存中交换的并不是K,V数据类型,而是为其分配的索引,这样做的好处就是减少内存中数据的挪动(即默认采用快排算法);
举个例子:
(原数据) (排序过程仅移动索引位置) (写入数据根据索引的顺序依次写入,从而实现降序排列)
xx1:ID1002 xx2:ID1002 ID1021
xx2:ID1021 ---> xx3:ID1021 ---> ID1015
xx3:ID1015 XX1:ID1015 ID1002