大数据Shuffle原理与实践 | 青训营笔记
这是我参与「第四届青训营」笔记创作活动的的第6天。
一、本堂课重点内容
-
Shuffle 是什么,为什么需要 Shuffle ,Shuffle 的基本过程是怎样的
-
介绍 Spark 中常用的 Shuffle 算子
-
Spark 中 Shuffle 的核心原理
-
Push Shuffle 各社区包括 Spark3.2 实现方案以及字节方案
二、详细知识点介绍
01. Shuffle 概述
1.1 MapReduce 概述
图源:教学PPT
1.1 Map 阶段
是在单机上进行的针对一小块数据的计算过程。
1.2 Shuffle 阶段
在 map 阶段的基础上,进行数据移动,为后续的 reduce 阶段做准备。
1.3 Reduce 阶段
对移动后的数据进行处理,依然是在单机上处理一小份数据
1.4 为什么 shuffle 对性能非常重要
- M*R 次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 数据压缩
01.总结
在大数据场景下,数据 shuffle 表示了不同分区数据交换的过程,不同的 shuffle 策略性能差异较大。
目前在各个引擎中 shuffle 都是优化的重点,在 spark 框架中,shuffle 是支撑 spark 进行大规模复杂数据处理的基石。
02. Shuffle 算子
2.1 Shuffle 算子分类
| repartition | ByKey | join | Distinct |
|---|---|---|---|
| coalesce | groupByKey | cogroup | distinct |
| repartition | reduceByKey | join | |
| aggregateByKey | leftOuterJoin | ||
| combineByKey | intersection | ||
| sortByKey | subtract | ||
| sortBy | subtractByKey |
2.2 Spark 中对 shuffle 的抽象
-
窄依赖:父 RDD 的每个分片至多被子 RDD 中的一个分片所依赖
-
宽依赖:父 RDD 中的分片可能被子 RDD 中的多个分片所依赖
2.2.1 Shuffle Dependency 构造
- 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.
—— Partitioner
- 两个接口
- numberPartitions
- getPartition
- 经典实现
- HashPartitioner
abstract class Partitioner extends Serializable{
def numPartitions:Int
def getPartition(key:Any):Int
}
—— Aggregator
- createCombiner:只有一个 value 的时候初始化的方法
- mergeValue:合并一个 value 到 Aggregator 中
- mergeCombiners:合两个 Aggregator
03. Shuffle 过程
3.1 Hash Shuffle - 写数据
每个 partition 会映射到一个独立的文件
—— 写数据优化
每个 partition 会映射到一个文件片段
3.2 Sort shuffle:写数据
每个 task 生成一个包含所有 partition 数据的文件
3.3 Shuffle - 读数据
每个 reduce task 分别获取所有 map task 生成的属于自己的片段
3.6 Shuffle Handle 与 Shuffle Writer 的对应关系
BypassMergeSortShuffleHandle -> BypassMergeSortShuffleWriter
SerializedShuffleHandle -> UnsafeShuffleWriter
BaseShuffleHandle -> SortShuffleWriter
3.7 Writer 实现 - BypassMergeSortShuffleWriter
- 不需要排序,节省时间
- 写操作的时候回打开大量文件
- 类似于 Hash Shuffle
—— UnsafeShuffleWriter
- 使用类似内存页储存序列化数据
- 数据写入后不再反序列化
- 只根据 partition 排序 Long Array
- 数据不移动
—— SortShuffleWriter
- 支持 combine
- 需要 combine 时,使用 PartitionedAppendOnlyMap,本质是个 HashTable
- 不需要 combine 时 PartitionedPairBuffer 本质是个 array
3.8 Reader 实现 - 网络时序图
- 使用基于 netty 的网络通信框架
- 位置信息记录在 MapOutputTracker 中
- 主要会发送两种类型的请求
- OpenBlocks 请求
- Chunk 请求或 Stream 请求
—— ShuffleBlockFetchIterator
- 区分 local 和 remote 节省网络消耗
- 防止OOM
- maxBytesInFlight
- maxReqsInFlight
- maxBlocksInFlightPerAddress
- maxReqSizeShuffleToMen
- maxAttemptsOnNettyOOM
—— External Shuffle Service
ESS 作为一个存在于每个节点上的 agent 为所 Shuffle Reader 提供服务,从而优化了 Spark 作业的资源利用率,MapTask 在运行结束后可以正常退出
3.9 Shuffle 优化使用的技术 - Zero Copy
DMA(Direct Memory Access):直接存储器存取,是指外部设备不通过 CPU 而直接与系统内存交换数据的接口技术。
—— Netty 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 优化
- 使用可以 map-side 预聚合的算子
04. 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 的位置
-
currentMapId:标识当前正在 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 中维护了两份 shuffle 数据的副本
4.6 Cloud Shuffle Service 架构
图源:教学PPT
4.7 实践案例 - CSS 优化
三、课程总结
- Shuffle 概述
- 什么是 shuffle,shuffle 的基本流程
- 为什么 shuffle 对性能影响非常重要
- Shuffle 算子
- 常见的 shuffle 算子
- 理解宽依赖和窄依赖,ShuffleDependency 及其相关组件
- Shuffle过程
- Spark 中 shuffle 实现的历史
- Spark 中主流版本的 shuffle 写入和读取过程
- Push shuffle
- Magnet Push Shuffle 的设计思路
- Cloud Shuffle Service 的设计实现思路