这是我参与「第四届青训营 」笔记创作活动的的第8天
接着前面的内容,今天我们来探讨一下Shuffle原理
什么是Shuffle
在开源实践的MapReduce当中,存在Map,shuffle,Reduce三个阶段
我们将一整份数据拆分成多个块,在多个机器上运行,这样就提高处理效率,而这些块中,不同的数据有不同处理方式,Map负责将这些数据筛选出来,然后将筛选后的数据移动到指定位置执行,数据移动的过程就可以称为shuffle,处理完后再整合到一起的过程就可以称为reduce。
为什么Shuffle优化对性能非常重要
在Shuffle过程中,主要有以下可优化的空间:
- m*r次网络连接
- 大量数据移动
- 数据丢失分险
- 可能存在大量的排序操作
- 大量的数据序列化,反序列化。
- 数据压缩
可以看到,任何一项的优化空间都很大,因此对Shuffle就成了各个计算引擎优化的重点,同时在Spark中,Shuffle也是支撑Spark大数据处理的基石。
Shuffle算子
这里我们将Shuffle分为四类
- repartition
- bykey 聚合操作产生
- join 通过join产生
- distinst 可以理解成特殊的bykey 主要由以下组成:
算子产生shuffle要发生数据移动,这个过程在Spark中被抽象成宽,窄依赖也就是上一节课上所提到的。
宽依赖对应对象 ShuffleDependency,它被2个RDD创建,分别是CoGroupedRDD和ShuffledRDD,而这两个RDD,又通过cogroup操作或combine操作生成。
Shuffledependency构造
其中发挥重要作用的是Aggregator结构
- Aggregator
它把reduce中的工作拿到map上来做。它有3个方法:
- createcombiner:只有一个value的时候初始化方法
- mergevalue:合并一个value到Aggregator中
- mergecombiner 合并两个Aggregator
shuffle过程
- 通过 Hash Shuffle写数据
但我们可以看出Hash shuffle写数据生成M x R个文件,这样生成的文件就太多了,文件太多情况会导致OM,所有我们引入了Sort Shuffle.
- 通过Sort shuffle写数据
这样做一个task只生成一个文件+index索引文件,大大减少了文件数目。
- 读数据
每个Reduce Task分别获取所有map task生成自己的片段。
Shuffle过程触发流程
graph LR
CollectAction --> SubmitJob --> GetDependencies --> RegisterShuffle
我们先来看一下Resigister Shuffle Resigister Shuffle做的事情 最重要就是创建了Shuffle handle,它根据不同场景创建不同Shuffle Handle,如下图:
每一个Handle都有一个对应的Writer,如下:
我们来看一下每个Writer是如何实现的。
- BypassMergeSortShuffleWriter
他不需要排序,比较节省时间,但写操作时会打开大量文件,其中partition不能太多默认是200,merge运用到zero copy方案。
- UnsafeShuffleWriter
不同于上面的实现,这个Writer使用了堆外内存,所一是Unsafe的,使用内存页储存序列化数据,且数据写入后不再反序列化,就减少了序列化反序列化的开销。
说完Writer的实现,再来看一下Reader实现,在Reader中,只有一个方法实现。
- SortShuffleWriter
它支持combine,在cmobine时,使用PartitionedAppendOnlyMap,本质上是一个HashTable,不需要Table是就相当与array。