Spark-核心流程-Shuffle

Spark Shuffle

1.Hash Shuffle

Hash Shuffle主要分两类:Hash Shuffle(普通机制),Consolidated Shuffle(合并机制)。

1.1.Hash Shuffle

Hash SHuffle经历了两个阶段,未优化和优化。区别在于基于Hash的Shuffle需不需要数据进行排序。

  • 未优化
    • Shuffle Write阶段:对数据分区,持久化形成一个ShuffleBlockFile,又称File Segment。持久化原因:减轻内存压力,并增加容错。
    • Shuffle Read阶段:拉取ShuffleBlockFile(FileSegment),完成后续计算。

在这里插入图片描述

Shuffle Write

上面简单讲述了未优化的Hash Shuffle简单工作流程,下面具体的区讲讲Shuffle Write这个阶段是如何工作的。

  • 首先对数据分区,然后将数据写入Bucket缓冲区(这一步就是所谓的分区!每个Task都维护多个缓冲区),每个缓冲区会生成一个对应的ShuffleBlockFile。

接下来有一个问题:ShuffleMapTask怎么决定那些数据写道那个缓冲区呢?

​ 答案:这个是根据分区算法决定的,可能是Hash算法,也可能是Range。

这种实现很简单,但是也有几个问题:

  1. 首先是在这个过程中,缓冲区会生成大量的ShuffleBlockFile。

    一般Spark的Job,以及最后切分出的Task数量都很多,这样生成大量文件存储到磁盘。

  2. 缓冲区的内存占用空间很大。

    如果数据规模比较大的情况下,内存可能承受不了,会出现OOM等问题

优化过后的Hash Shuffle(也就是Consolidated Shuffle)其实是为了解决第一个问题,小文件过多的问题。因为写磁盘的操作没有缓冲区会非常影响IO速度,因此缓冲区问题未得到解决。下面讲一下小文件过多的问题是如何进行优化的。

**优化思路:**同一CPU内核执行的ShuffleMapTask可以共用一个Bucket缓冲区,然后写到一个ShuffleBlockFile中。这样最后Worker节点生成的文件数降到了CPU内核数乘以Reduce任务的数量,大大减少了文件量。

在这里插入图片描述

Shuffle Read

Shuffle Read阶段主要任务就是拉取数据,那么需要考虑的问题是什么时候开始拉取数据文件(FileSegment)?

  1. 理论上,只要有一个Task执行完成就可以开始拉取数据了;
  2. 实际上,Spark必须要等父Stage执行完才能执行子Stage。因此必须所以ShuffleMap Task执行完成才可以拉取数据。
  3. 拉取的数据的数据先存入Buffer缓冲区,所以一次性拉取的FileSegment不能太大,如果拉取的数据太大会溢写到磁盘。

拉取过来一个Buffer的数据,就可以开始聚合了,但是这里又有一个问题,每次拉取的数据,怎样才能实现全局聚合?

答案:Spark的作法是使用HashMap,聚合操作实际上是 **map.put(key, map.get(key)+1) ** ,这个代码的意思是将map中聚合过的数据get出来相加,然后在put回去。这样等所有的数据拉取完,也就到了一个map中,完成聚合。

2.Sort Shuffle

Sort Shuffle分为三类:SortShuffleWrite(普通机制),BypassMergeSortShuffleWrite(Bypass机制),UnsafeSortShuffle(外部排序机制—谨慎使用)

2.1.SortShuffleWrite

还是之前提到的两个问题:1. 产生文件数量过多;2. Write缓存开销较大。下面讲一下Sort Shuffle是如何做的。

Spark引入了类似Hadoop MapReduce的Shuffle机制。该机制每一个ShuffleMapTask不会为后续的任务单独创建文件,而是将所有的Task结果写入同一个文件,并生成对应的索引文件。

也就是说Hash Shuffle为每一个Reduce产生一个文件,但是Sort Shuffle只产生一个按找Reducer ID排序可索引的文件。这样只要获取有关文件的相关数据库的位置信息,就可以读取到指定Reduce的数据。

数据会先写入一个内存数据结构(外部排序数据结构 ExternalSorter)中,它使用了Spark内存中的一部分作为缓冲区,用于存储需要排序的数据。

ExternalSorter工作方式是这样的:当数据流到达SortShuffleWriter时,它们首先被写入到一个内存缓冲区中。每写一条数据到内存数据结构后,都会判断一下缓冲区是否达到了一个预设的阈值,SortShuffleWriter就会将这些数据写入到磁盘上,并且清空内存数据结构(为什么要清空?)。这个过程称为Spill。在Spill的过程中,SortShuffleWriter会尽量保证数据在磁盘上的顺序与它们在内存中的顺序一致(根据Key排序)。这样,在后续的读取操作中,就可以避免进行额外的排序操作。

一个Task将所有数据写入内存数据结构过程中会发生多次磁盘溢写,生成多个临时文件。最后将所有临时文件联合内存一起归并,从而减少内存的使用量。文件数量显著减少,并且减少了Write缓存所使用的内存空间,同时避免了GC风险和频率(这一点如何解释?)。

此外,由于一个Task只对应一个磁盘文件,意味着Task为下游Stage的Task准备的数据都在一个文件里。因此需要单独写一份索引文件,索引标识了下游各个Task的数据在文件中的Start Offset和End Offset(偏移量)。

在这里插入图片描述

具体执行流程如下:

  • Map Task 的计算结果会写入到外部排序数据结构(ExternalSorter,Shuffle Write的缓冲区)里面,内存数据结构默认5M

  • 在Shuffle的时候有一个定时器,不定期估算ExternalSorter的大小,当内存结构中的数据超过 5M 时,比如现在内存结构中的数据为 5.01M ,那么他会申请 5.01 * 2 - 5 = 5.02M 内存给内存数据结构。

  • 如果申请成功则不会溢写,如果失败则发生溢写到磁盘

  • 在溢写之前内存数据结构中的数据会进行排序(要不然为啥叫外部排序内存结构)

  • 然后开始溢写到磁盘,写磁盘时批量(Batch)写入,一个Batch时一万条数据。

  • Map Task执行完成后,会将这些磁盘小文件合并成一个大的磁盘文件,同时生成一个索引文件。

  • ReduceTask拉取数据前,先解析索引文件,后根据索引文件再去进行拉取对应数据。

2.2.BypassMergeSortShuffleWriter

BypassMergeSortShuffleWriter 和 Hash Shuffle 中的 HashShuffleWriter 实现基本一致,唯一区别时Map端的多个输出文件会汇总成一个文件,所有分区的数据会合并成一个文件。合并成一个索引文件,为了标识到每个分区的起始位置。注意这种方式不适合有太多分区,因为过程中会并发打开所有分区对应的临时文件(大量磁盘IO),会对文件系统带来很大压力。

具体实现

​ 给下游文件的每个分区创建一个临时文件,将数据根据Key进行hash(也可以自定义分区器),然后根据Hash值找到对应分区的输出文件句柄,将Key直接写入对应磁盘文件(另类排序,将相同的Key放在一起),没有使用内存中的Buffer缓冲区。最后把所有的临时分区文件拷贝到最终的输出文件中(拷贝的话留有副本,用完怎么办?)并且记录每个分区文件的起始写入位置,写入索引文件中。

在这里插入图片描述

2.3. UnsafeShuffleWriter

这种方式还挺复杂。简单讲就是UnsafeShuffleWriter 里面维护着一个 ShuffleExternalSorter,用来做外部排序,外部排序就是要先部分排序数据并把数据输出到磁盘,然后最后再进行 Merge 全局排序, 既然这里也是外部排序,跟 SortShuffleWriter 有什么区别呢?这里只根据 Record 的 Partition ID 先在内存 ShuffleInMemorySorter 中进行排序, 排好序的数据经过序列化压缩输出到换一个临时文件的一段,并且记录每个分区段的 seek 位置,方便后续可以单独读取每个分区的数据,读取流经过解压反序列化,就可以正常读取了。

在这里插入图片描述

整个过程就是不断地在 ShuffleInMemorySorter 插入数据,如果没有内存就申请内存,如果申请不到内存就 Spill 到文件中,最终合并成一个依据 Partition ID 全局有序的大文件。

3.总结

3.1.Shuffle逻辑流

HadoopMR 是基于文件的数据结构,Spark是基于RDD的数据结构,性能比Hadoop高。

3.2.Shuffle Sort

​ Hadoop Map Reduce是基于排序的,进入combine()和reduce()必须先排序。这样好处在于combine()/reduce() 可以处理大规模的数据,Mapper对每段数据先排序,Reducer对排序后的每段数据做归并 在Spark中,数据在ReduceTask端一定不排序,在MapTask端可以根据设置或者算子决定排序还是不排序。如果不需要排序就退化成Hash-Based Shuffle的方式,省去排序开销。

3.3.Shuffle Fetch
Fetch后数据存放位置

Hadoop Reduce将数据拉取到同一Reduce分区后归并排序合并成一个文件,直接将文件存在磁盘上。

Spark Shuffle Read拉取的数据首先肯定是放在Reduce端的内存缓冲区中,现在的是实现都是内存+磁盘的方式(数据结构使用 ExternalAppendOnlyMap),当然也可以通过 spark.shuffle.spill=false 来设置只能使用内存。使用 ExternalAppendOnlyMap 的时候如果内存的使用达到一定临界值,会首先尝试在内存中扩大 ExternalAppendOnlyMap(内部有实现算法),如果不能扩容的话才会 Spill 到磁盘。

什么时候拉取操作

Hadoop MR 模型只要某个 MapTask 溢写合并完成 ReduceTask 就可以开始拉取。

Spark MR 模型需要等 MapTask 全部工作完成才能进行拉取。

拉取操作与数据计算粒度

Hadoop Reduce 的 Fetch 是粗粒度的,Hadoop Reducer Fetch 到的数据后先暂时存放到 Buffer 中,当 Buffer 快满时才进行 combine() 操作。

Spark Shuffle 的 Fetch 是细粒度的,Reducer 对 Mapper 端的数据是边拉取边聚合。

adoop Reducer Fetch 到的数据后先暂时存放到 Buffer 中,当 Buffer 快满时才进行 combine() 操作。

Spark Shuffle 的 Fetch 是细粒度的,Reducer 对 Mapper 端的数据是边拉取边聚合。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值