这是我参与「第四届青训营 」笔记创作活动的第6天
1.Shuffle概述
Shuffle是什么,为什么需要Shuffle,Shuffle的基本过程是怎么样的
①MapReduce概述
-
在开源实现的MapReduce中,存在Map、Shuffle、Reduce三个阶段
-
在Map和Reduce中被分成一块一块的单独处理的数据叫做partition
-
Map阶段:是在单机上进行的针对一小块数据的计算过程
-
Shuffle阶段:在map阶段的基础上,进行数据移动,为后续的reduce阶段做准备
-
Reduce阶段:对移动后的数据进行处理,依然是在单机上处理一小份数据
②为什么shuffle对性能非常重要
- M*R次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作:将内存中直观的数据转化成二进制数据流放到文件中,再从文件中读到内存中来转换成对象,会消耗大量CPU
- 数据压缩、解压缩
③小结
批计算的发展流程:
在大数据场景下,数据Shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异大,目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle是支撑spark进行大规模复杂数据处理的基石
2.Shuffle算子
介绍Spark中常用的Shuffle算子
①shuffle算子分类
spark中会产生shuffle的算子大概可以分成4类
②shuffle算子应用
③Saprk中对shuffle的抽象-宽依赖、窄依赖
算子会产生shuffle是因为它需要做数据移动,在spark中,它被抽象为宽依赖和窄依赖
-
窄依赖:父RDD的每个分片至多被子RDD中的一个分片所依赖
-
宽依赖:父RDD中的分片可能被子RDD中的索格分片所依赖
-
当出现宽依赖时,spark会把运算拆分成两个stage,一个是map stage,一个是reduce stage,两个stage之间很产生shuffle操作
算子内部的依赖关系
-
ShuffleDependency的创建场景
-
当CoGroupedRDD被创建时(其创建场景:)
-
当调用Cogroup操作时
- fullOuterJoin、rightOuterJoin、leftOuterJoin
- join
-
-
当ShuffledRDD被创建时(其创建场景:)
-
当调用combineByKeyWithClassTag以及其他transform操作时
- combineByKey
- reduceByKey
-
Colalesce
-
sortByKey
- sortBy
-
-
-
于是得到一个树形结构(一个更合理的算子分类)
Shuffle Dependency构造函数包含的变量
- 一个key-value对形式的RDD
- Partitioner:负责给定一个key时产生一个key对应的分区
- Serializer:把一个对象映射成一个二进制的数据流,或把二进制数据映射成对象
- key是否需要排序的flag
- Optional Aggregator
- 描述shuffle是否有mapSideCombine的flag
Shuffle Dependency构造-Partitioner
-
负责把key映射成一个数字,这个数字代表某一个具体的分区
-
Partitioner是一个抽象类,有两个接口:
- numberPartitions(分区个数)
- getPartition(输入为key,输出为key对应的分区)
-
经典实现
- HashPartitioner(大多数情况下的默认实现),在构造时需要传入总的partition的数量
Shuffle Depenfdency构造-Aggregator
-
是shuffle时重要的性能优化器:把部分reduce的工作让map来做
-
三个方法
- createCombiner:只有一个value时初始化的方法
- mergeValue:合并一个value到Aggregator中
- mergeCombiners:合并两个Aggregator
3.Shuffle过程
Spark中shuffle的核心原理
①Hash Shuffle-写数据
每个partition会映射到一个独立的文件,每个Map Task都会为Partition创建一个buffer,写满了就flush到磁盘里,最终会生成M*R个文件
随着数据的增大,会对文件系统造成压力,不仅生成的文件多,同时打开的文件也多
Hash Shuffle-写数据优化
- 每个partition会映射到一个文件片段
- 生成的文件个数为c*R(c是文件核数)
②Sort shuffle-写数据
每个task生成一个包含所有partition数据的文件,不再给每个partition一个shuffle,当内存满了,通过排序的方式,将相同partition的数据放在一起
③Shuffle-读数据
每个reduce task分别获取所有map task生成的属于自己的片段
④Shuffle过程的触发流程
前五行只是记录运算过程的对象,不会触发任何计算,只有当执行collect(action算子)时才会触发计算,触发流程如下图所示:
⑤Shuffle Handle的创建
Register Shuffle时做的最重要的事情是根据不同条件创建不同的Shuffle Handle
⑥Shuffle Handle与Shuffle Writer的对应关系
Writer实现-BypassMergeShuffleWriter
- 不需要排序,节省时间(只适用于partition较少的情况)
- 写操作时会打开大量的文件
- 类似于Hash Shuffle
Writer实现-UnsafeShuffleWriter
-
使用类似内存页储存序列化数据
-
数据写入后不再反序列化
-
Unsafe:使用的是对外内存,而不是Java本身的HBase;使用对外内存的原因:没有Java对象模型内存开销,没有垃圾回收的开销,这在性能上是更优的
-
它把对外内存分成若干内存页,一个内存页写满就新建一个内存页,直到内存写满,spill到磁盘上,最后merge会将spill文件和内存文件进行merge
-
对于对外内存的管理
- record序列化往对外内存写时,还会在堆内有一个Long Array记录原信息(record属于哪个partition,记录在第几页,内存页偏移量多少)
- spill后会触发排序,因为要把相同partition的放在一起,这个排序只发生在array上,而不管记录在对外内存的record,数据不移动
Writer实现-Sort Shuffle Writer
- 支持combine
- 需要combine时,使用PartitionedAppendOnlyMap,本质是个hashTable
- 不需要combine时PartitionPairBuffer本质是个array