【shuffle/内存模型】spark(七)超详细mareduce shuffle和spark Shuffle讲解、以及spark比mapreduce快在哪些方面

零. shuffle概述

Shuffle描述着数据从map task输出到reduce task输入的这段过程。
 
通常shuffle分为两部分:Map阶段的数据准备和Reduce阶段的数据拷贝处理。一般将在map端的Shuffle称之为Shuffle
Write,在Reduce端的Shuffle称之为Shuffle Read。
 
在分布式情况下,reduce task需要跨节点去拉取其它节点上的map task结果。这一过程将会产生网络资源消耗和内存,磁盘IO的消耗。

 

一. MapReduce的原理

1. 分片输入:计算分片与分配

1.1 计算输入分片,一个分片针对一个map任务

在进行map计算之前,mapreduce会根据输入文件计算输入分片(input split),每个输入分片(input split)针对一个map任务,输入分片(input split)存储的并非数据本身,而是存储分片长度和记录数据的位置的数组。

 

1.2 分片的大小设定

假如我们设定hdfs的块的大小是64mb,如果我们输入有三个文件,大小分别是3mb、65mb和127mb,那么mapreduce会把3mb文件分为一个输入分片(input split),65/127mb则是两个输入分片(input split)。

 

1.3 优化:如何做到数据均匀

换句话说我们如果在map计算前做输入分片调整,例如合并小文件,那么就会有5个map任务将执行,而且每个map执行的数据大小不均,这个也是mapreduce优化计算的一个关键点。

 

2. map:计算、写入环形缓冲区

2.1 maptask

每一个maptask处理一个分片数据并写到一个环形缓冲区。maptask调用context.write(k,v)方法将数据输出到缓行缓冲区,不是直接将数据写到reduce。

如下图:maptask的调用细节:

在这里插入图片描述
 

2.2 Shuffle Write:写入环形缓冲区的过程

数据写入:缓冲区以equator为分界线,左边写入元数据、右边写入原始数据。
 
空间剩余20%:当整个缓冲区写入80%的内存时开始溢写到磁盘,同时剩下的空间继续写入数据。
 
新的equator和缓冲区重复利用:当剩余的20%缓冲区写完之后且溢写也结束,元数据移动到数据左边,形成和原始数据背对背的形式,形成新的equator分界线,继续写数据。

注意:如果20%的空间写满了,溢写还没有完成,则处于阻塞状态。

 

3. Shuffle:分区内外排序、文件merge

3.1 文件溢写

溢写前的排序:

每一个溢写的过程都会产生一个文件,在数据溢写之前会根据原始数据(进行快速排序)调整元数据的位置,具体地,先根据数据的分区进行排序,然后再根据map-key进行排序。

溢写时:

通过排好序的元数据,将文件写到磁盘**(第一次落地)**。

 

3.2 溢写文件合并

合并前,针对每个spill文件的分区情况进行归并排序,排好序之后最后合成一个merge文件,即一个taskmap最后会合并成一个文件**(第二次落地)**。

在这里插入图片描述

 

4. reduce:拉取数据、分区merge、计算

4.1 shuffle read:拉取数据

当MRAPPMaster跟踪到只要有一个maptask运行完时,就启动全部的ReduceTask,然后开始拉取属于自己分区的数据。

4.2 数据合并

reduceTask将不同MapTask相同分区文件数据进行归并排序。

4.3 数据reduce

根据key为组,每一组调用一个setup,run和cleanup方法,去聚合数据。每一个maptask输出一个文件。

 

小结

1. mapreduce的调度

mapreduce的(maptask、reducetask )整个阶段都由MRAPPMaster来跟踪和调度。

2. combiner的优化

a. 对于每一个MapTask,数据溢写的前,数据排好序后进行数据的combine操作,然后再溢写,这样提前做的聚合会减少数据的传输和spill文件产生的数量。

b. 对于归并排序文件合并前也可以使用combiner,进一步聚合减少数据量。

适用范围:

比如有数据:opt(opt(1, 2, 3), opt(4, 5, 6))。如果opt为求和、求最大值的话,可以使用,但是如果是求中值的话,不适用。

3. shuffle的缺陷

如果某一个阶段计算失败,整个job需要重新计算(spark的优化点:RDD的血缘,checkpoint等)。
整个计算过程中,数据的频繁落地,消耗大量的IO(spark的优化点:基于内存计算)。

 
 

二. spark 的shuffle

1. spark shuffle的历史

Spark Shuffle 分为两种:一种是基于 Hash 的 Shuffle;另一种是基于 Sort 的 Shuffle。

在Spark 1.2以前:
默认的shuffle计算引擎是HashShuffleManager

主要目的
使用这个引擎的主要目的是为了避免不需要的(快速排序、归并排序)排序,MapReduce将sort作为固定步骤,有许多不需要排序的任务,MR也会进行排序,这造成了许多不必要的问题。

弊端:产生大量的中间磁盘文件,进而会有大量的磁盘IO,而这些操作影响了运行性能。

 
在Spark 1.2以后的版本:
默认的ShuffleManager改成了SortShuffleManager

相较于HashShuffleManager来说,主要的改进是:每个Task在进行shuffle操作时,虽然也会产生较多的临时磁盘文件,但是最后会将所有的临时文件合并(merge)成一个磁盘文件,因此每个Task就只有一个磁盘文件。

在下一个stage的shuffle read task拉取自己的数据时,只要根据索引读取每个磁盘文件中的部分数据即可。

 

2. hashShuffleManager

2.1. 未优化的HashShuffleManager

在这里插入图片描述

shuffle write阶段与产生的文件数

如上图map阶段(一个stage)一共有4个task,其中两个task在一个executor中,一个exector只有一核也就是在同一个时间下只有一个task在工作。

一个task产生的文件数由下一个stage的task数决定,且一个文件只能对应下游一个task。如上图下一个stage有三个(reduce)task,那每一个(map)task就会成产生三个磁盘文件。

那上图map stage总共会产生:223=12个文件。再假设下游stage有100个task,上游有9个executor,每个executor有5个task,那上游stage会产生的文件数就有:95100=4500,这是非常庞大。

 

shuffle read阶段与批聚合

当map stage阶段的数据都写完之后,reduce stage中的task开始shuffle read。

下游的每一个task开始拉取对应自己(相同)的数据,shuffle read的过程是一边拉取一边聚合。每个task都有自己的buffer,每次都只能拉取与buffer缓冲大小相同的数据,然后通过内存中的一个map进行聚合操作。每聚合一批数据之后,再拉取下一批数据进行聚合,直到所有的数据都拉取完,并得到最终结果。

 

2.2. 优化的HashShuffleManager- consolidate 机制

为了避免产生过多的小文件,可以设置一个参数:spark.shuffle.consolidateFiles=true。

consolidate与shuffleFileGroup

开启consolidate之后,在shuflle write过程中,会产生shuffleFileGroup,一个shuffleFileGroup会对应多个磁盘文件,文件数量和下游stage的task数量是相同的。
 
每一个运行的task都会创建一个shuffleFileGroup。之后再进行数据写入时,可以复用这个shuffleFileGroup。

磁盘文件复用

consolidate 机制允许下游不同的 task 复用同一批磁盘文件,这样就可以有效将多个 task
的磁盘文件进行一定程度上的合并,从而大幅度减少磁盘文件的数量,进而提升 shuffle write 的性能。

文件数量

假设第一个stage有50个task,第二个stage有100个task,总共有 10 个 Executor(Executor CPU 个数为 1),每个 Executor 执行 5 个 task。
优化后的文件数:每个 Executor(单核) 此时只会创建 100 个磁盘文件,所有 Executor 只会创建 1000 个磁盘文件。

 

2.3. hash的shuffle的优缺点:

优点:

  • 可以省略不必要的排序开销。
  • 避免了排序所需的内存开销。

缺点:

  • 生产的文件过多,会对文件系统造成压力。
  • 大量小文件的随机读写带来一定的磁盘开销。
  • 数据块写入时所需的缓存空间也会随之增加,对内存造成压力。

 

3. SortShuffle

SortShuffleManager 的运行机制主要分成三种:

  1. 普通运行机制
  2. bypass 运行机制
  3. Tungsten Sort 运行机制

3.1. 普通运行机制

在这里插入图片描述

3.1.1 数据存入内存结构

该模式下数据先会写入一个内存结构中,对于不同的shuffle算子,可能选用不同的数据结构:

a. 对于reduceBy等聚合类的shuffle算子,选用Map数据结构,一边通过Map进行聚合,一边写入内存;
b. 对于join来说,会选用Array数据结构,直接写入内存。

接着每写入一条数据就会判断是否达到了某个临界阈值,如果达到临界阈值,就尝试将内存数据结构中的数据溢写到磁盘,然后清空内存数据结构。

3.1.2 数据排序和溢写

数据溢写之前,会根据key对内存结构中已有的数据进行排序(有些计算这个排序是不必要的)。排序过后,分批写入磁盘文件中。

默认的batch数量是10000条,也就是每10000条进行一次刷新写入到磁盘文件。
写入磁盘是通过BuffedOutputStream实现的,减少IO次数,提升性能。

3.1.3 文件合并与索引文件

数据写入内存中的过程会有多次溢写的过程,也就是会产生多个临时文件,最后会将之前所有的临时文件进行合并

此时会将之前所有临时文件读取出来,然后再写入到最终的磁盘文件中。最终一个task只产生一个文件,也就是说该task为下游stage提供的数据都在这一个文件中,因此还有一份索引文件:标识了下游各个task拉取数据的start和end offset。

3.1.4 产生的文件数

假设上游有50个task,下游有100个task,因为一个task只会产生一个文件,所以总共会产生50个文件。

 

3.2 bypass运行机制

在这里插入图片描述

回退方案

当reduce端任务比较少时,基于hash shuffle的实现要明显快于基于sort shuffle的实现机制。因此spark提供了一种回退机制:bypass运行机制,触发条件如下:

  • 当reducer端任务数少于spark.shuffle.sort.bypassMergeThreshold时,使用hash shuffle机制。
  • 不是聚合类的算子。
和hashShuffleManager的区别

shuffle write过程:对数据的key进行hash计算,然后根据hash值将数据先写内存,然后再溢写到磁盘。最后将所有临时文件合并为一个文件,并创建一个索引文件。

和(未经优化的)hashShuffleManager的不同是:一个task最后只产生一个文件。
与普通 SortShuffleManager不同的是:1. 磁盘写机制不同;2. 不会进行排序。

对于启用bypass机制来说最大的好处是,在shuffle write过程中,不需要进行数据的排序,节省了这部分的性能开销。

 

3.3. Tungsten Sort Shuffle ing

默认情况下,Spark 默认开启的是基于 SortShuffle 实现机制,但实际上,基于 Tungsten Sort Shuffle 实现机制也是 SortShuffleManager。
内部使用的具体的实现机制,是通过提供的两个方法进行判断的:

不基于 Tungsten Sort 时,通过 SortShuffleWriter.shouldBypassMergeSort判断是否需要回退到 Hash 风格的 Shuffle 实现机制,当该方法返回的条件不满足时,继续通过SortShuffleManager.canUseSerializedShuffle 方法判断是否需要采用基于 Tungsten Sort Shuffle 实现机制,而当这两个方法返回都为 false,即都不满足对应的条件时,会自动采用普通运行机制

 

3.4 基于 Sort 的 Shuffle 机制的优缺点

优点:

  • 小文件数据量大量减少,1个task只产生一个磁盘文件,mapper端的内存占用变少。
    缺点:
  • 当Mapper阶段task数量过大时,也会产生很多小文件,reducer端会进行多次的IO读写,导致大量的内存消耗和GC。
  • 强制了在 Mapper 端必须要排序,即使数据本身并不需要排序,浪费了资源。

 
 

三. spark到底比MapReduce快在哪里

1. 计算模型的不同

当涉及到计算不管是mapreduce或者是spark都需要将数据加载到内存中然后计算,而他们的不同是:

MapReduce总是会将中间的计算结果写到磁盘,然后reducer再去读取磁盘,这样MR的计算总是伴随着大量的IO操作。
 
对于spark来说,它的编程模型是:RDD和DAG。其中DAG会记录整个job的stage以及RDD之间的依赖关系,而计算的中间结果会通过RDD的方式放在内存中

  1. 当job中的中间结果不需要落地时,整个job的运行就是基于内存计算的,而这要远远快于mapreduce。
  2. checkpoint:触发检查点保存中间结果,减少容错的开销。
  3. cache:将常用的RDD算子结果进行缓存,重复使用,加快计算。
  4. 广播变量

 

2. MR进程 / Spark线程

MR的task对应的是一个个的Java进程,而Spark则是对应的是线程,Spark上每个task的生命周期、开销等都比Hadoop更轻量级。

  1. 多进程的好处是能够细粒度的控制每个任务占用的资源,但是每次启动都需要消耗资源(申请资源、启动进程)
  2. spark是通过复用线程池中的线程来减少启动,关闭task的开销,但其实也会出现当多个task运行在一个进程中,会出现严重的资源争用,难以细粒度的控制每个task的资源量。

 

3. shuffle机制不同

MapReduce在Shuffle时需要花费大量时间进行排序,排序在MapReduce的Shuffle中似乎是不可避免的;
 
Spark在Shuffle时则只有部分场景才需要排序,支持基于Hash的分布式聚合,shuffle机制更加job情况能够灵活的选择。

 

4. 网络通信

Spark的网络通讯模块通过Java Native Interface转给原生的Netty来做,而不是像Hadoop那样,让Hadoop框架自己负担。这样,Spark实际上借助了Netty来提升了网络传输性能。
 

5. 编程语言

Spark主要是用Scala语言实现的。scala不修改变量,所以不需要考虑线程安全,以及死锁的情况,所以并发效率会很高。

 

 
参考:
Spark的两种核心Shuffle详解
为什么Spark比MapReduce快?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

roman_日积跬步-终至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值