这是我参与「第四届青训营 」笔记创作活动的的第6天
0x00 Shuffle 概述
0.1 Mapreduce 概述
map:将数据拆分成分布式状态
shuffle:将拆分后的数据进行分类移动
reduce:将数据分散至机器进行并发操作
0.2 Map阶段
在单机上进行的针对一小块数据的计算过程
0.3 Shuffle阶段
在map阶段的基础上,进行数据移动,为后续的reduce阶段做准备
0.4 Reduce阶段
对移动后的数据进行处理,依然是在单机上处理一小份数据
0.5 为什么shuffle如此重要?
数据shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大。目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle是支撑spark进行大规模复杂数据处理的基石。
0x01 Shuffle 算子
1.1 常见的触发shuffle的算子
- repartition
coalesce、repartition
- ByKey
groupByKey、reduceByKey、aggregateByKey、combineByKey、sortByKeysortBy
- Join
cogroup、join
- Distinct
1.2 算子内部的依赖关系
1.2.1 Shuffle Dependency
- 创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle相关的信息
- 构造函数:
- A single key-value pair RDD, i.e. RDD[Product2[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.
1.2.2 Partitioner
- 用来将record映射到具体的partition的方法
- 接口:
- numberPartitions
- getPartition
1.2.3 Aggregator
- 在map侧合并部分record的函数
- 接口:
- createCombiner:只有一个value的时候初始化的方法
- mergeValue:合并一个value到Aggregator中
- mergeCombiners:合并两个Aggregator
0x02 Shuffle 过程
2.1 HashShuffle
2.1.1 Hash Shuffle - 写数据
每个partition会映射到一个独立的文件
2.1.2 Hash Shuffle - 写数据优化
每个partition会映射到一个文件片段
2.2 SortShuffle
2.2.1 Sort Shuffle - 写数据
每个task生成一个包含所有partiton数据的文件
2.3 Shuffle - 读数据
每个reduce task分别获取所有map task生成的属于自己的片段
2.4 Shuffle 过程的触发流程
2.5 Shuffle Handle 的创建
Shuffle Register会根据不同的条件决定注册不同的ShuffleHandle
2.6 ShuffleWriter的实现
三种ShuffleHandle对应了三种不同的ShuffleWriter的实现
2.7 ShuffleReader网络请求流程
- 使用netty作为网络框架提供网络服务,并接受reducetask的fetch请求
- 首先发起openBlocks请求获得streamId,然后再处理stream或者chunk请求
2.8 Reader实现
2.8.1 Reader实现 - ShuffleBlockFetchIterator
- 区分local和remote节省网络消耗
- 防止OOM
- maxBytesInFlight
- maxReqsInFlight
- maxBlocksInFlightPerAddress
- maxReqSizeShuffleToMem
- maxAttemptsOnNettyOOM
2.8.2 Reader实现 - External Shuffle Service
为了解决Executor为了服务数据的fetch请求导致无法退出问题,我们在每个节点上部署一个External Shuffle Service,这样产生数据的Executor在不需要继续处理任务时,可以随意退出。
2.9 shuffle优化
- 避免shuffle ——使用broadcast替代join
- 使用可以map-side预聚合的算子
- 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.targetPostShuffleInputSize
- spark.reducer.maxSizeInFlight
- spark.reducer.maxReqsInFlight spark.reducer.maxBlocksInFlightPerAddress
- Shuffle 倾斜优化
- 倾斜影响:
- 作业运行时间变长
- Task OOM导致作业失败
- 倾斜方法举例:
- 增大并发度
- AQE
- 倾斜影响:
- 零拷贝
- sendfile+DMA gather copy
- Netty 零拷贝
- 可堆外内存,避免 JVM 堆内存到堆外内存的数据拷贝。
- CompositeByteBuf 、 Unpooled.wrappedBuffer、 ByteBuf.slice ,可以合并、包装、切分数组,避免发生内存拷贝
- Netty 使用 FileRegion 实现文件传输,FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
0x03 Push Shuffle
3.1 为什么需要Push Shuffle
- 数据存储在本地磁盘,没有备份
- IO 并发:大量 RPC 请求(M*R)
- IO 吞吐:随机读、写放大(3X)
- GC 频繁,影响 NodeManager
3.2 各公司优化思路
- Facebook: cosco
- LinkedIn:magnet
- Uber:Zeus
- Alibaba: RSS
- Tencent: FireStorm
- Bytedance: Cloud Shuffle Service
- Spark3.2: push based shuffle
3.2 Magnet实现流程
主要为边写边push的模式,在原有的shuffle基础上尝试push聚合数据,但并不强制完成,读取时优先读取push聚合的结果,对于没有来得及完成聚合或者聚合失败的情况,则fallback到原模式。
3.3 Cloud Shuffle Service
3.3.1 Cloud Shuffle Service架构
- Zookeeper WorkerList [服务发现]
- CSS Worker [Partitions / Disk | Hdfs]
- Spark Driver [集成启动 CSS Master]
- CSS Master [Shuffle 规划 / 统计]
- CSS ShuffleClient [Write / Read]
- Spark Executor [Mapper + Reducer]
3.3.2 Cloud Shuffle Service写入流程
3.3.3 Cloud Shuffle Service读取流程
3.3.4 Cloud Shuffle Service AQE
在聚合文件时主动将文件切分为若干块,当触发AQE时,按照已经切分好的文件块进行拆分。