大数据 Shuffle 原理与实践 - 复习笔记
这是我参与「第四届青训营 」笔记创作活动的的第7天!
1. shufle概述
1.1 MapReduce 概述
- 2004年谷歌发布了《MapReduce:Simplified Data Processing on Large Clusters》论文
- 在开源实现的MapReduce中,存在Map、Shufle、Reduce三个阶段。
1.2 Map阶段
- Map阶段,是在单机上进行的针对一小块数据的计算过程
1.3 Shuffle阶段
- Shufle 阶段,在 map 阶段的基础上,进行数据移动,为后续的 reduce 阶段做准备。
1.4 Reduce 过程
- reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据
1.5 为什么 shuffle 对性能非常重要
- M*R次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩
1.6 总结
- 在大数据场景下,数据 shufle表示了不同分区数据交换的过程,不同的shufle策略性能差异较大。
- 目前在各个引擎中shufle都是优化的重点,在spark框架中,shuffle 是支撑 spark 进行大规模复杂数据处理的基石。
2. shufle算子
2.1 Shuffle 算子分类
Spark 中会产生shuffle的算子大概可以分为4类
| repartition | ByKey | join | Distinct |
|---|---|---|---|
| coalesce | groupByKey | cogroup | distinct |
| repartition | reduceByKey | join | |
| aggregateByKey | leftOuterJoin | ||
| combineByKey | intersection | ||
| sortByKey | subtract | ||
| sortBy | subtractByKey |
2.2 Shuffle 算子应用
- Spark源码中RDD的单元测试
- Spark源码中PairRDDFunctions的单元测试
val text = sc.textFile("mytextfile. txt")
val counts = text.flatMap(line=>line. split(" "))
.map(word => (word,1)).reduceByKey(_+_)
counts. collect
2.3 Spark中对shuffle的抽象-宽依赖、窄依赖
- 窄依赖:父RDD的每个分片至多被子RDD中的一个分片所依赖
- 宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖
2.4 算子内部的依赖关系
- ShuffleDependency
- CoGroupedRDD
- Cogroup
- fullOuterJoin、rightOuterJoin、leftOuterJoin
- join
- Cogroup
- ShuffledRDD
- combineByKeyWithClassTag
- combineByKey
- reduceByKey
- Coalesce
- sortByKey
- sortBy
- combineByKeyWithClassTag
- CoGroupedRDD
2.4.1 Shuffle Dependency 构造
- A single key-value pair RDD,i.e.RDDIProduct2[K,V]],
- Partitioner(available as partitioner property),
- Serializer,
- Optional key ordering(of Scala's scala.math.Ordering type),
- Optional Aggregator,
- mapSideCombine flag which is disabled(i.e.false)by default.
2.4.2 Shuffle Dependency 构造-Partitioner
- 两个接口
- numberPartitions
- getPartition
- 经典实现
- HashPartitioner
abstract class Partitioner extends Serializable {
def numPartitions:Int
def getPartition(key:Any):Int
}
class HashPartitioner(partitions: Int) extends Partitioner{
require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative";
def numpartitions: Int = partitions
def getPartition(key: Any): Int = key match{
case null => 0
case _ => Utils. nonNegativeMod(key.hashCode, numPartitions)
}
2.4.3 Shuffle Dependency构造-Aggregator
- createCombiner:只有一个value的时候初始化的方法
- mergeValue:合并一个value到Aggregator中
- mergeCombiners:合并两个Aggregator
3. shufle过程
3.1 Hash Shuffle-写数据
每个partition会映射到一个独立的文件
3.1.1 Hash Shuffle-写数据优化
每个partition会映射到一个文件片段
3.2 Sort shuffle:写数据
每个task 生成一个包含所有partiton数据的文件
3.3 Shuffle-读数据
每个reduce task分别获取所有map task生成的属于自己的片段
3.4 Shuffle 过程的触发流程
val text = sc.textFile("mytextfile.txt")
val counts = text
.flatNap(line => 1ine, split(" "))
.map(word => (word,l))
.reducelyKey(_+_)
counts.collect
3.5 Shuffle Handle的创建
Register Shuffle 时做的最重要的事情是根据不同条件创建不同的 shufle Handle。
3.6 Shuffle Handle 与 Shuffle Writer的对应关系
3.7 Writer 实现 - BypassMergeShuffleWriter
3.7.1 Writer 实现 - BypassMergeShuffleWriter
- 不需要排序,节省时间
- 写操作的时候会打开大量文件
- 类似于Hash Shuffle
3.7.2 Writer 实现 - UnsafeShuffleWriter
- 使用类似内存页储存序列化数据
- 数据写入后不再反序列化
- 只根据 partition 排序Long Array
- 数据不移动
3.7.3 Writer 实现 - SortShuffleWriter
- 支持combine
- 需要combine时,使用PartitionedAppendOnlyMap,本质是个HashTable
- 不需要combine时PartitionedPairBuffer本质是个array
3.8 Reader 实现-网络时序图
- 使用基于netty的网络通信框架
- 位置信息记录在MapOutputTracker中
- 主要会发送两种类型的请求
- OpenBlocks请求
- Chunk请求或Stream请求
3.8.1 Reader 实现-ShuffleBlockFetchlterator
- 区分local和remote节省网络消耗
- 防止OOM
- maxByteslnFlight
- maxReqslnFlight
- maxBlocksInFlightPerAddress
- maxReqSizeShuffleToMem
- maxAttemptsOnNettyOOM·
3.8.2 Read 实现-External Shuffle Service
ESS作为一个存在于每个节点上的agent为所有Shuffle Reader提供服务,从而优化了Spark作业的资源利用率,MapTask在运行结束后可以正常退出
3.9 Shuffle优化使用的技术-Zero Copy
- 可堆外内存,避免JVM堆内存到堆外内存的数据拷贝。
- CompositeByteBuf、Unpooled.wrappedBuffer、ByteBuf.slice,可以合并、包装、切分数组,避免发生内存拷贝
- Netty 使用 FileRegion 实现文件传输,FileRegion 底层封装FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
3.10 常见问题
- 数据存储在本地磁盘,没有备份
- IO并发:大量RPC请求(M*R)
- IO吞吐:随机读、写放大(3X)
- GC频繁,影响 NodeManager
3.11 Shuffle 优化
- 避免shuffle
- 使用broadcast替代join
- 使用可以map-side预聚合的算子
3.12 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.maxBlocksInFlightPerAddress
3.13 Shuffle 倾斜优化
- 什么叫shuffle倾斜
- 倾斜影响
- 作业运行时间变长
- Task OOM导致作业失败
- 常见的倾斜处理办法
- 提高并行度
- 优点:足够简单
- 缺点:只缓解、不根治
- 提高并行度
- Spark AQE Skew Join
AQE 根据 shuffle文件统计数据自动检测倾斜数据,将那些倾斜的分区打散成小的子分区,然后各自进行join。
3.14 案例-参数优化
- ad show
- number of files read:840,042
- number of total tasks:5,553
- size of files read:203.3 TiB
- number of output rows:128,676,054,598
select
ad_type,
sum(click),
sum(duration)
from ad_show
where date = '20220101'
group by
ad_type
3.15参数调整
- spark.sql.adaptive.shuffle.targetPostShufflelnputSize:64M->512M
- spark.sql.files.maxPartitionBytes:1G->40G
4. Push Shuffle
4.1 为什么需要Push Shuffle?
- Avg IO size太小,造成了大量的随机IO,严重影响磁盘的吞吐
- M*R次读请求,造成大量的网络连接,影响稳定性
4.2 Push Shuffle 的实现
- Facebook:cosco
- Linkedin:magnet
- Uber:Zeus
- Alibaba:RSS
- Tencent:FireStorm
- Bytedance:CSS
- Spark3.2:push based shuffle
4.3 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
4.4 Magnet 可靠性
- 如果Map task输出的Block没有成功Push到magnet上,并且反复重试仍然失败,则reduce task直接从ESS上拉取原始block数据
- 如果magnet上的block因为重复或者冲突等原因,没有正常完成merge的过程,则reduce task直接拉取未完成merge的block
- 如果reduce拉取已经merge好的block失败,则会直接拉取merge前的原始block
- 本质上,magnet中维护了两份shufle数据的副本
4.5 Cloud Shuffle Service 架构
- Zookeeper WorkerList[服务发现]
- CSS Worker[Partitions/Disk IHdfs]
- Spark Driver[集成启动CSS Master]
- CSS Master[Shuffle 规划/统计]
- CSS ShuffleClient[Write/Read]
- Spark Executor[Mapper+Reducer]
5. 课程总结
- Shufle 概述
- 什么是shufle,shuffle的基本流程
- 为什么shufle对性能影响非常重要
- Shufle算子
- 常见的shufle算子
- 理解宽依赖和窄依赖,ShuffleDependency及其相关组件
- Shuffle过程
- Spark中shuffle实现的历史
- Spark中主流版本的shuffle写入和读取过程
- Push shuffle
- Magnet Push Shuffle的设计思路
- Cloud Shufle Service的设计实现思路