shuffle总结
shuffle是mapreduce编程模型中连接map阶段和reduce阶段的最重要环节。是Reduce Task从Map Task拉取数据的一个过程。除了自定义的map和reduce函数,剩下的几乎都是由框架帮我们完成。而shuffle就是发生在我们自定义map函数输出<k2,v2>到reduce自定义函数获取<k2,v2s>的过程,中间的处理过程对我们来说几乎是透明的。因此要想搞懂shuffle,就必须要搞懂这中间过程框架是如何处理的。Map阶段的shuffle过程。
(1)map阶段的入口类是由MapTask类的run()方法开始的
(2)在该run()方法中,先会判断使用新api还是旧api来运行mapper任务。
(3)如果使用新api则调用runNewMapper()方法,在这个方法中会通过反射,创建驱动代码中所设置的相关对象。
例如:自定义Mapper类、输入文件解析类InputFormat、将每个InputSplit解析成一个个K,V对的RecordReader类
上下文类MapContext。
(4)最终调用Mapper类的run()方法开始执行。
(5)该run()方法会不断调用我们自定义的map函数。
(1)当我们调用自定义map函数中中的context.write()方法后,将key_value对写入,这个键值对就进入了框架处理过程。
(2)在经过一系列的write()方法调用后最终会根据reduce task的数量而执行不同的逻辑。如果reduce task数量大于0则
调用MapOutputBuffer对象的collect方法将k,v写入环形内存缓冲区。如果reduce task数量小于0则直接使用
DirectMapOutputCollector对象的collect方法将k,v输出到hdfs。
(3)默认情况reduce task数量都是大于0。因为会选择将数据写入缓冲器。在写入缓冲区之前会先调用
partitioner.getPartition(key, value, partitions)方法,该方法会给k,v对标记一个分区索引号,作用是为了环形内存缓冲区中的
每一个k,v对属于哪个分区,后期在做排序和spill写时候有用。获取到分区索引号后才开始调用MapOutputBuffer对象的collect方法,
这个方法相当于一个生产者,负责向缓冲区中写入数据,该方法内部使用了Java中的do..while语法,先执行一次循环
体,然后根据条件判断是否继续执行。
(4)在这个MapOutputBuffer对象有一个成员变量byte[] kvbuffer;该变量就是所谓的环形内存缓冲器,其实就是一个字
节数组。如果kvbuffer的大小到达阈值后,就启动后台线程SpillThread将内存中的数据写入磁盘文件
中,SpillThread线程相当于消费者,用来处理缓冲区中的数据。
(5)在SpillThread对象的run()方法中最关键的是会调用sortAndSpill(),大致执行流程
1, 为了保证数据写入安全,先加锁。
2,根据分区索引号创建溢写文件。
3,利用快速排序,对kvbuffer中的数据进行排序,排序方式先按照partition分区索引号对数据排序,然后在对每个分区中的数据按照key进行排序。(此处就用到了第4步预先给每个k,v对绑定的分区索引号)
4,然后按照分区索引号由小到大把每个分区的数据写入预先根据分区索引号创建好的溢写文件中。如果用户设置了combiner则在写入之前,先对每个分区中的数据进行一次combiner操作,然后在写入文件,写入文件后会为该文件创建一个索引,并保存到一个索引内存缓冲区中。
5,全部写完后关闭io流,如果索引内存缓冲区大于1M则会将这些信息写入一个溢写索引文件中。
6,释放锁。
(5)当该Map Task上的所有数据全部处理完成后,Map Task会将所有溢写文件写入到一个大文件中,并且按照分区顺序进行排序。
(好处,当Reduce Task拉取数据时,可以将大量随机读,改成有序读,从而提高效率)
====================到此map阶段处理完毕========================
Reduce Task阶段的shuffle过程。
(1)与Map Task一样,Reduce Task入口类为Reduce Task的run()方法。该方法主要创建Shuffle类的对象,通过该Shuffle对象开始shuffle。
(2)启动一个拉取数据过程监听器,用来监听整个拉取数据进度,然后通过配置文件指定的拉取线程数,创建指定的线程数组,然后循环启动线程,每个线程以http形式开始拉取数据。每个线程会把拉取过来的数据存放到reduce task所在的tasktracker节点内存中,当内存中的数据到达一定阈值会将内存中的数据写到磁盘中,写之前会先排序,合并。
(3)等待所有数据拉取完成,关闭监听线程,关闭所有拉取线程。然后判断当前内存中是否还有剩余数据,如果有则对内存剩余数据进行排序合并,并写入到磁盘文件中。最终把所有磁盘文件进行排序合并,按照key排序,并把相同key的value放到一起。传递给reduce 方法。
shuffle优化:
配置方面:(1)增大map阶段的缓冲区大小。
(2)map阶段输出结果使压缩;压缩算法使用lzo。
(3)增加reduce阶段copy数据线程数。
(4)增加副本数,从而提高计算时的数据本地化。
程序方面:(1)在不影响计算结果的情况下建议使用combiner。
(2)输出结果的序列化类型尽量选择占用字节少的类型。
架构方面:将http改为udp,因为http还要进行3次握手操作。