Spark shuffle

1、什么是shuffle

Shuffle中文翻译为“洗牌”,需要Shuffle的关键性原因是某种具有共同特征的数据需要最终汇聚到一个计算节点上进行计算。把父RDD中的KV对按照Key重新分区,从而得到一个新的RDD。也就是说原本同属于父RDD同一个分区的数据需要进入到子RDD的不同的分区。

2、为什么需要shuffle

在分布式计算框架中,数据本地化是一个很重要的考虑,即计算需要被分发到数据所在的位置,从而减少数据的移动,提高运行效率。Map-Reduce的输入数据通常是HDFS中的文件,所以数据本地化要求map任务尽量被调度到保存了输入文件的节点执行。但是,有一些计算逻辑是无法简单地获取本地数据的,reduce的逻辑都是如此。对于reduce来说,处理函数的输入是key相同的所有value,但是这些value所在的数据集(即map的输出)位于不同的节点上,因此需要对map的输出进行重新组织,使得同样的key进入相同的reducer。 shuffle移动了大量的数据,对计算、内存、网络和磁盘都有巨大的消耗,因此,只有确实需要shuffle的地方才应该进行shuffle,否则尽可能避免shuffle。

3、哪些常用的算子会发生shuffle

(1)去重操作:Distinct等。

(2)聚合,byKey类操作,reduceByKey、groupByKey、sortByKey等。byKey类的操作要对一个key,进行聚合操作,那么肯定要保证集群中,所有节点上的相同的key,移动到同一个节点上进行处理。

(3)排序操作:sortByKey等。

(4)重分区操作:repartition、repartitionAndSortWithinPartitions、coalesce(shuffle=true)等。重分区一般会shuffle,因为需要在整个集群中,对之前所有的分区的数据进行随机,均匀的打乱,然后把数据放入下游新的指定数量的分区内。

(5)集合或者表操作:join、cogroup等。两个rdd进行join,就必须将相同join key的数据,shuffle到同一个节点上,然后进行相同key的两个rdd数据的笛卡尔乘积。

4、Shuffle可能面临的问题

运行Task的时候才会产生Shuffle(Shuffle已经融化在Spark的算子中了)。

(1) 数据量非常大;几千甚至上万台机器进行Shuffle的数据量会很大,从其他各台机器上收集过来数据的时候,网络传输量会很恐怖

(2)数据如何分类,即如何Partition,Hash、Sort、钨丝计算;不同的Partition的不同实现,他会影响集群规模的大小,会影响内存的使用,会影响性能等等方面,也就有了Shuffle几个不同的净化阶段

(3) 负载均衡(数据倾斜);因为采用不同的Shuffle的方式对数据进行不同的分类,而分类之后数据又分到不同的节点上进行计算,如果Shuffle分类不恰当,会导致负载均衡,也就是数据倾斜

(4)网络传输效率,需要在压缩和解压缩之间做出权衡,序列化和反序列也是要考虑的问题;如果压缩,则需要解压缩,解压缩需要消耗CPU,所以需要衡量带宽和CPU解压的时间,做出正确的权衡

说明:具体的Task进行计算的时候尽一切最大可能使得数据具备Process Locality的特性。因为这是它运行最快的方式,数据在内存中,也就是默认采取的方式,如果迫不得已,数据不能全部放在内存中,从实际生成角度讲(即不具备内存本地性);退而求次是增加数据分片,减少每个Task处理的数据量导致任务运行的批次更多,任务更多。

cache本身具有风险,Memory溢出风险,它被其他计算占用掉内存的风险,导致重新计算,除非计算特别复杂,计算链条特别长,可能有必要为了容错,为了再次数据复用,来进行中间结果的持久化,否则的话,尤其是持久化到disk时,还不如在内存中直接计算,这样的速度有可能比从磁盘中读取曾经计算结果来的更快2,度磁盘I/O是一个高风险的动作,读内存分享会降低很多。

在一个Stage内部,不持久化中间结果,数据丢失重新计算依赖的RDD;但是在产生Shuffle的时候,会产生网络通信,这是需要持久化。

持久化默认情况下放在磁盘中,也可以调整Spark的框架,将数据放在内存中,现在一般放在Local FileSystem上面,也可以放在Tachyon中,这些都可以通过调整Spark的配置和改造Spark源码来实现。

5、Hash Shuffle

 工作机制根据Shuffle的前面的Stage的最后一个final RDD,依据Partition把数据分成不同的类,按照Key的hashcode,然后按照一定的业务逻辑规则(例如,假如下一个Stage有3个并行任务,最简单的就是取模3运算,分成3种类型的数据)无需排序,性能很好

注意:key不能是Arraykey如果是Array,则就无法非常友好的计算具体的hashcode值

注意:不需要排序的Hash Shuffle不一定比需要排序的Sorted Shuffle速度更快!如果数据规模比较小的情形Hash Shuffle会比Sorted Shuffle速度快(很多)!但是如果数据量大,此时Sorted Shuffle一般都会比Hash Shuffle快(很多)

数据量大的情况下,Sorted Shuffle比Hash Shuffle快的原因:如果数据规模比较大,可能Hash Shuffle无法处理,因为hash的方式时会有key和句柄之类,还有许多小文件,此时,磁盘的性能会成为瓶颈,内存也会变成瓶颈。Sorted Shuffle会极大地节省磁盘、内存的访问,更有利于更大规模的数据运算

每个ShuffleMapTask(除了最后一个Stage里面的是ResultTask类型,其他前面的每个都是ShuffleMapTask)会根据key的哈希值计算出当前的key需要写入的Partition,然后把决定后的结果写入当单独的文件,此时会导致每个Task产生R(指下一个Stage的并行度)个文件,如果当前的Stage中有M个ShuffleMapTask,则会M*R个文件!!!

注意:Shuffle操作绝大多数情况下都要通过网络,如果Mapper和Reducer在同一台机器上,此时只需要读取本地磁盘即可

每个任务都产生R个小文件,由于其需要将数据分成几种不同类型,就是下一个Stage的具体的Task会读取的与自己相关的数据,因为已经分好类了,此时会产生M*R个小文件,那么下一个Stage就会通过网络根据Driver的注册信息(由于上一个Stage写过的内容会注册给Driver),然后询问Driver上一个Stage具体的输出在哪里,以及哪些属于该Stage的部分,通过网络读取数据;同时Shuffle的数据不一定都需要通过网络(有可能在同一台机器上。

hash Shuffle的两个缺点:①Shuffle前会产生海量的小文件于磁盘上,此时会产生大量耗时的低效的IO操作,②内存不够用,由于内存中需要保存海量的文件操作句柄和临时缓存信息,如果数据处理规模比较庞大的话,内存不可承受,出现OOM等问题。

6、consalidate机制

为了改善上述的问题(同时打开过多文件导致Writer Handler内存使用过大以及产生过度文件导致大量的随机读写带来的效率极为低下的磁盘IO操作),Spark后来推出了Consalidate机制,来把小文件合并。那么怎么合并呢?假设Map并行运行2个core,实际上map的任务假设有10个,这10个任务,根据cpu的个数来决定具体产生多少文件)此时Shuffle时文件产生的数量为cores*R,对于ShuffleMapTask的数量明显多于同时可用高的并行Cores的数量的情况下,Shuffle产生的文件会大幅度减少,会极大的降低OOM的可能,也就是性能变得更好。(合并后虽然变大了,但是有索引)。

consalidate机制减少了文件,同时也减少了文件句柄的数量;但对于并行度非常高时,及R值特别大时,还是很麻烦

7、 Sorted Shuffle

采用Hash的方式的适用场景是数据规模相对比较小,而且不需要排序。Consalidate一定程度上解决了该问题,但仍不彻底,SortedBasedShuffle更好的解决了该问题。

SortedBasedShuffle首先,每个ShuffleMapTask不会为每个Reducer生成一个单独的文件,它会将所有的结果写到一个文件里,同时生成一个Index索引文件,每个Reducer可以根据这个Index索引文件取得它所需要处理的数据,这样就避免产生大量文件,没有了大量文件,也就没有了大量的文件句柄,节省了内存;同时由于磁盘上文佳变少了,而且有Index索引,不用随机的去读写,而是顺序的disk I/O,带来了低延迟,节省了内存;另一方面,减少了GC风险和频率,而减少具体的文件数量可以避免同时些多个文件是给系统带来的压力,这就是优势所在。

具体的实现:ShuffleMapTask会按照Key相应的Partition的ID进行Sort,如果属于同一个Partition的Key,本身不进行Sort,因此对不需要sort的操作来说,如果内存不够用,他就会把那些已经排序的内容写到外部disk,结束的时候再进行归并排序(merge-sort),为高效读取这些file Seagate,它有一个Index文件,会记录不同的Partition的位置信息,BlockManager也会对它的寻址算法进行优化性的实现。归并排序最优是打开10-100个文件。

最后生成文件时需要同时生成Index索引文件。 对具体的ShuffleMapTask,它外部有具体的归并排序方式,mergeSort,sort之后会产生两个文件,这两个文件其中一个是Index索引文件,一个是存放具体的Task的输出内容,在Reducer端读取数据的时候,其实首先访问Index,具体在工作的时候,BlockManager首先访问Index,通过Index去定位具体文件内容。避免了大量文件句柄,节省内存。

采用Sort方式集群的规模和数据的计算规模就不受限制了。

7.1 为什么要使用Sorted Shuffle

1、Shuffle一般包含两阶段任务:第一部分,产生Shuffle数据的阶段(Map阶段,额外补充,需要实现ShuffleManager中getWriter来写数据(数据可以BlockManager写到Memory、Disk、Tachyon等,例如像非常快的Shuffle,此时可以考虑把数据写在内存中,但是内存不稳定,建议采用MEMORY_AND_DISK方式));第二部分,使用Shuffle数据的阶段(Reduce阶段,额外的补充,需要实现ShuffleManager的getReader,Reader会向Driver去获取上一下Stage产生的Shuffle数据)

2、Spark的Job会被划分成很多Stage:如果只有一个Stage,则这个Job就相当于只有一个Mapper阶段,当然不会产生产生Shuffle,适合于简单的ETL;如果不止一个Stage,则最后一个Stage就是最终的Reducer,最左侧的第一个Stage就仅仅是整个Job的Mapper,中间所有的任意一个Stage是其父Stage的Reducer且是其子Stage的Mapper。

3、Spark Shuffle在最开始的时候只支持Hash-based Shuffle:默认Mapper阶段会为Reducer阶段的每一个Task单独创建一个文件来保存该Task中要使用的数据,但是在一些情况下(例如数据量非常大的情况)会造成大量文件(M*R,其中M代表Mapper中的所有的并行任务数量,R代表Reducer中所有的并行任务数量)的随机磁盘I/O操作且会性能大量的Memory消耗(极易造成OOM),这是致命的问题,因为第一不能够处理大规模的数据,第二Spark不能够运行在大规模的分布式集群上!后来的改善方式是加入了Shuffle Consolidate机制来将Shuffle时候产生的文件数量减少到C*R个(C代表在Mapper端同时能够使用的Cores的数量,R代表Reducer中所有的并行任务数量),但是此时如果Reducer端的并行数据分片过多的话则C*R可能已经过大,此时依旧没有逃脱文件打开过多的厄运!!!

4、为了让Spark在更大规模的集群上更高性能处理更大规模的数据,于是就引入了Sort-based Shuffle! 从此以后(Spark 1.1版本开始),Spark可以胜任任意规模(包含PB级别及PB以上的级别)的大数据的处理,尤其是随着钨丝计划的引入和优化,把Spark更快速的在更大规模的集群处理更海量的数据的能力推向了一个新的巅峰

5、 Spark 2.1版本只支持2中种类型Shuffle,默认是sort,因为我们还实现ShuffleManager接口可以根据自己的业务实际需要最优化的使用自定义的Shuffle实现

6、Sort-Based Shuffle产生的文件数量是2M(M:map阶段的中并行的Partition的总数量,其实就是Mapper端Task的总数量(Task总数量和实际并行运行的数量是2回事,因为可能100万个分片,导致100万个task,但是实际只能运行1千个,就排队去))个临时文件

7.2 bypass运行机制

bypass运行机制的触发条件如下:

(1)shuffle map task数量小于spark.shuffle.sort.bypassMergeThreshold参数的值。

(2)不是聚合类的shuffle算子。此时,每个task会为每个下游task都创建一个临时磁盘文件,并将数据按key进行hash然后根据key的hash值,将key写入对应的磁盘文件之中。当然,写入磁盘文件时也是先写入内存缓冲,缓冲写满之后再溢写到磁盘文件的。最后,同样会将所有临时磁盘文件都合并成一个磁盘文件,并创建一个单独的索引文件。

该过程的磁盘写机制其实跟未经优化的HashShuffleManager是一模一样的,因为都要创建数量惊人的磁盘文件,只是在最后会做一个磁盘文件的合并而已。因此少量的最终磁盘文件,也让该机制相对未经优化的HashShuffleManager来说,shuffle read的性能会更好。

而该机制与普通SortShuffleManager运行机制的不同在于:第一,磁盘写机制不同;第二,不会进行排序。也就是说,启用该机制的最大好处在于,shuffle write过程中,不需要进行数据的排序操作,也就节省掉了这部分的性能开销。

bypass运行机制的SortShuffleManager工作原理如下图:

8、shuffle历史文件个数对比

Shuffle产生的临时文件的数量的变化一次为:

(1)Basic Hash Shuffle:M(map端并行task的总数)*R(下一个Stage的并行度);

(2)Consalidate方式的Hash Shuffle:C(cpu的core个数)*R;

(3)Sort-based Shuffle:2M(map端并行task的总数)。

 

发布了82 篇原创文章 · 获赞 10 · 访问量 6554
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览