spark教程(13)-shuffle介绍

shuffle 简介

shuffle 描述了数据从 map task 输出到 reduce task 输入的过程,shuffle 是连接 map 和 reduce 的桥梁;

shuffle 性能的高低直接影响了整个程序的性能和吞吐量,因为在 分布式 情况下,reduce task 需要跨节点去拉取其他节点上 map task 的结果,这需要消耗网络资源、内存 IO 和磁盘 IO;

shuffle 可分为两部分:map 阶段的数据准备和 reduce 阶段的数据拷贝处理,一般 map 端的 shuffle 称之为 Shuffle Write,reduce 端的 shuffle 称之为 Shuffle Read

Hadoop MapReduce Shuffle

hadoop 中的 shuffle 过程应该是很多 shuffle 的基础,互相参考,所以请先了解 hadoop 的 shuffle,参考 我的博客 hadoop-mapreduce 详解

Spark Shuffle

Stage

首先我们了解一个概念,Stage,在讲 Spark 原理时,提了一下

1. 一个 task 会被划分成 多个 Stage,送给 task 调度器;

2. Stage 中是高效快速的 Pipeline 的计算模式;

3. 宽依赖之间会划分 Stage;

4. 不同的 Stage 之间会有 Shuffle

看图来理解一下

Stage 1 和 Stage 3 之间存在 shuffle;Stage 2 和 Stage 3 之间也存在 shuffle 

Shuffle 发展

在 spark 中,shuffle 的计算、执行和处理 由 ShuffleManager 负责;

ShuffleManager 随着 spark 的发展有两种方式,分别为 HashShuffleManager 和 SortShuffleManager,因此 spark 的 shuffle 有 Hash 和 Sort 两种;

在 spark1.2,使用 HashShuffleManager,由于 Hash 存在很多问题,spark1.2 之后,改为 SortShuffleManager

HashShuffleManager

由于已经被弃用,简单介绍下

HashShuffleManager 的运行机制分为两种,一种是 普通运行机制,一种是 合并运行机制,合并运行机制是对普通机制的优化;

1) 普通运行机制

1. 上图中有一个 task,两个 Executor,每个 Executor 有两个线程,由于 Executor 是单核的,故每个 Executor 某个时刻只能运行一个线程;

2. 每个线程对数据进行 partition 后,把不同分区存入 内存 buffer;

3. buffer 填满后,spill 到磁盘;

4. 最后生成了 3(个区)x2(线程数)x2(Executor 数) = 12 个小文件;

5. 每个 reduce (3 个分区,需要 3 个 reduce)从不同节点拉取自己需要的 文件;

存在的问题

1. 生成了很多小文件,map 端 write 和 reduce 端 read 会消耗大量的 IO ;

2. 过多的小文件,在拉取过程中会消耗大量的网络资源,并且很耗时;

3. 由于内存中需要保存海量文件操作句柄和临时信息,如果数据处理的规模比较庞大的话,内存不可承受,会出现 OOM 等问题

2) 合并运行机制

针对 普通运行机会,它的改进在于,在分区之后,把不同 task 的分区结果存储到一个文件中,这样一定程度上减少了 map 后的 文件数量;

但问题依然很大

SortShuffleManager

SortShuffleManager 的运行机制也分两种:一种是 普通运行机制,另一种是 bypass 运行机制

1)普通运行机制

1. 数据先写入一个内存数据结构中

  // 根据不同的 shuffle 算子,选择不同的数据结构, 如果是 reduceByKey 等聚合算子,会选择 map 数据结构,先通过 map 进行聚合,在写入内存;如果是 join 等普通算子,选用 array 数据结构,直接写入内存

2. 每写入一条数据,会计算该数据结构的大小,如果达到临界值,就溢写到磁盘,然后清空该数据结构

3. 在溢写时,首先根据 key 进行排序

4. 然后分批写入磁盘,也就是有个 buffer,每次 batch 1w 条数据,当 buffer 区满了,再一次性写入磁盘,这样减少 IO,提升性能

5. merge:一个 task 将所有数据写入内存数据结构的过程中,会发生很多次溢写操作,产生多个 临时文件;最后需要将这些文件全部合并为一个文件;

  // 分别读取每个临时文件,依次写入一个文件;

  // 一个 task 只对应一个 文件,ruduce 端只需拉取一个文件即可;

  // 由于一个 task 溢写的那个总文件实际上对应了后面多个 reduce,所以需要有一个 索引文件,指定在文件中每个 reduce 对应的数据的 offset,start and end

2)bypass 运行机制

这种机制只在某种条件下执行,触发条件为

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

2. 非聚合类算子

 

1. 这种机制会给每个 reduce 产生一个临时文件,其实跟 普通机制的 HashShuffleManager  一样,根据 key 的 hash 值写入不同的文件,当然写入时 还是 buffer,然后 buffer 满了才一次性写入

  // 此时产生了大量的小文件

2. 将所有文件进行合并,也会产生一个 索引文件,记录每个 reduce 端的所需数据的 offset

VS 普通机制的 HashShuffleManager:最终合并为一个大文件和一个索引文件

VS 普通机制的 SortShuffleManager:1. shuffle write 机制不同;2. 没有排序

本文只是做了简单介绍,其实还有很多更深的内容没有涉及,后续有时间再说

参考资料:

https://www.cnblogs.com/itboys/p/9226479.html#top

猜你喜欢

转载自www.cnblogs.com/yanshw/p/11717765.html