大数据 Shuffle 原理与实践 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第5天
重点内容:
一、Shuffle概述
1、MapReduce概述
-
Map阶段,是在单机上进行的针对一小块数据的计算过程。
-
Shuffle 阶段,在map阶段的基础上,进行数据移动,将同种类型的数据归到一起,为后续的reduce阶段做准备
-
Reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据
Shuffle对性能非常重要体现在以下几个方面:
- M*R次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩
在大数据场景下,数据shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大。 目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle 是支撑spark进行大规模复杂 数据处理的基石。
二、Shuffle算子
1、Shuffle算子分类
Spark 中会产生shuffle的算子大概可以分为4类
-
repartition
- coalesce、repartition
-
ByKey
- groupByKey、reduceByKey、aggregateByKey、combineByKey、sortByKeysortBy
-
Join
- cogroup、join
-
Distinct
- distinct
应用:
val text = sc.textFile("mytextfile.txt") val counts = text .flatMap(line => line.split(" ")) .map(word => (word,1)) .reduceByKey(_+_) counts.collect
2、Spark中对shuffle的抽象 - 宽依赖、窄依赖
- 窄依赖
父RDD的每个partition至多对应一个子RDD分区。
- 宽依赖
父RDD的每个partition都可能对应多个子RDD分区。
3、Shuffle Dependency主要构造变量
-
A single key-value pair RDD, i.e. RDD[Product2[K, V]],
-
Partitioner (available as partitioner property)--给定key产生分区,
有两个接口:
1、numberPartitions
2、getPartition
-
Serializer,
-
Optional key ordering (of Scala’s scala.math.Ordering type),
-
Optional Aggregator,
-
mapSideCombine flag which is disabled (i.e. false) by default.
Aggregator
在进行Shuffle时是一个重要的性能优化器。
- createCombiner:只有一个value的时候初始化的方法
- mergeValue:合并一个value到Aggregator中
- mergeCombiners:合并两个Aggregator
三、Shuffle过程
Shuffle实现的发展历程
- Spark 0.8及以前Hash Based Shuffle
- Spark 0.8.1 为Hash Based Shuffle引入File Consolidation机制
- Spark 0.9 引入ExternalAppendOnlyMap
- Spark 1.1 引入Sort Based Shuffle,但默认仍为Hash Based Shufle
- Spark 1.2 默认的Shuffle方式改为Sort Based Shuffle
- Spark 1.4 引入Tungsten-Sort Based Shuffle
- Spark 1.6 Tungsten-Sort Based Shuffle并入Sort Based Shuffle
- Spark 2.0 Hash Based Shuffle退出历史舞台
Hash Shuffle -写数据
每个partition会映射到一个独立的文件
Hash Shuffle - 写数据优化
每个partition会映射到一个文件片段
Sort shuffle:写数据
每个task生成一个包含所有partiton数据的文件
Shuffle - 读数据
每个reduce task分别获取所有map task生成的属于自己的片段
Shuffle过程的触发流程
val text = sc.textFile("mytextfile.txt") val counts = text .flatMap(line => line.split("")) .map(word => (word,1)) .reduceByKey(_+_) counts.collect
Shuffle优化使用的技术: Netty Zero Copy
-
可堆外内存,避免JVM堆内存到堆外内存的数据拷贝。
-
CompositeByteBuf 、Unpooled. wrappedBuffer、ByteBuf.slice ,可以合并、包装、切分数组,避免发生内存拷贝
-
Netty 使用FileRegion实现文件传输,FileRegion 底层封装了FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
常见问题
- 数据存储在本地磁盘,没有备份
- IO并发:大量RPC请求(M*R)
- IO吞吐:随机读、写放大(3X)
- GC频繁,影响NodeManager
Shuffle参数优化
-
spark.default.parallelism &&spark.sql.shuffle.partitions
-
spark.hadoopRDD.ignoreEmptySplits
-
spark.hadoop.mapreduce.input.fileinputformat.split.minsize
-
spark.sql.file.maxPartitionBytes
-
spark.sql.adaptive.enabled&&spark.sql.adaptive.shuffle.targetPostShufflelnputSize
-
spark.reducer.maxSizelnFlight
-
spark.reducer.maxReqsInFlight
-
spark.reducer.maxBlockslnFlightPerAddress
四、Push Shuffle
为什么需要Push Shuffle ?
-
Avg IO size太小,造成了大量的随机IO,严重影响磁盘的吞吐
-
M*R次读请求,造成大量的网络连接,影响稳定性
Push Shuffle的实现
- Facebook:cosco
- Linkdin:magnet
- Uber:Zeus
- Alibaba:RSS
- Tencent:FireStorm
- Bytedance:CSS
- Spark3.2:push based shuffle
Magnet实现原理
- Spark driver组件,协调整体的shuffle操作
- map任务的shuffle writer过程完成后,增加了-个额外的操作push. merge,将数据复制一份推到远程shuffle服务上
- magnet shuffle service是一个强化版的ESS。将隶属于同一个shuffle partition的block,会在远程传输到magnet后被merge到一个文件中
- reduce任务从magnet shuffle service接收合并好的shuffle数据
- bitmap:存储已merge的mapper id,防止重复merge
- position offset:如果本次block没有正常merge,可以恢复到上一个block的位置
- currentMapld:标识当前正在append的block,保证不同mapper 的block能依次append
Magnet可靠性
- 如果Map task输出的Block没有成功Push到magnet上,并且反复重试仍然失败,则reduce task直接从ESS上拉取原始block数据
- 如果magnet上的block因为重复或者冲突等原因,没有正常完成merge的过程,则reducetask直接拉取未完成merge的block
- 如果reduce拉取已经merge好的block失败,则会直接拉取merge前的原始block本质上,magnet中维护了两份shuffle数据的副本