这是我参与「第四届青训营 」笔记创作活动的的第6天
今天是大数据专场基础班的第六次课,主要内容是介绍大数据 Shuffle 的原理与实践,主要分为下面四个板块。
一、 shuffle概述
1. MapReduce概述
- 2004年谷歌发布了《MapReduce: Simplified Data Processing on LargeClusters》论文
- 在开源实现的MapReduce中,存在Map、Shuffle、Reduce三个阶段
2. Map阶段
- Map阶段,是在单机上进行的针对─小块数据的计算过程
3. Shuffle阶段
- Shuffle阶段,在map 阶段的基础上,进行数据移动,为后续的reduce阶段做准备
4. Reduce过程
- reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据
5. 为什么shuffle对性能非常重要
- M * R次网络连接
- 大量的数据移动
- 数据丢失风险
- 可能存在大量的排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩
总结
- 在大数据场景下,数据shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大
- 目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle是支撑spark进行大规模复杂数据处理的基石
二、 shuffle算子
1. Shuffle算子分类
- Spark中会产生shuffle的算子大概可以分为4类
2. Shuffle算子应用
- Spark原码中RDD的单元测试
- Spark原码中PairRDDFunctions的单元测试
l val text = sc.textFile ("mvtextfile .txt")
2 val counts = text
3 .flatMap(line => line.split(" "))
4 .map(word =>(word ,1) )
5 .reduceByKey(_+_)
6 counts.collect
3. Spark中对shuffle的抽象–宽依赖、窄依赖
- 窄依赖: 父RDD的每个分片至多被子RDD中的一个分片所依赖
- 窄依赖: 父RDD的每个分片至多被子RDD中的一个分片所依赖
4. 算子内部的依赖关系
- ShuffleDependency
- coGroupedRDD
- Cogroup
- fullOuterJoin、rightOuterJoin、leftOuterJoin
- join
- Cogroup
- ShuffledRDD
- combineByKeyWithClassTag
- combineByKey
- reduceByKey
- Coalesce
- sortByKey
- sortBy
- combineByKeyWithClassTag
- coGroupedRDD
5. 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
5.1 Partitioner
- 用来将record映射到具体的partition的方法
- 接口
- numberPartitions
- getPartition
- 接口
5.2 Aggregator
- 在map侧合并部分record的函数
- 接口
- createCombiner: 只有一个value的时候初始化的方法
- mergeValue: 合并一个value到Aggregator中
- mergeCombiners: 合并两个Aggregator
- 接口
三、 shuffle过程
1. Hash Shuffle-写数据
- 每个partition会映射到一个独立的文件
2. Hash Shuffle -写数据优化
- 每个partition会映射到一个文件片段
3. Sort shuffle :写数据
- 每个task生成一个包含所有partiton数据的文件
4. Shuffle -读数据
- 每个reduce task分别获取所有map task生成的属于自己的片段
5. Shuffle过程的触发流程
6. Shuffle Handle的创建
- Register Shuffle时做的最重要的事情是根据不同条件创建不同的shuffle Handle
7. Shuffle Handle 与Shuffle Writer的对应关系
8. Writer 实现
8.1 BypassMergeShuffleWriter
- 不需要排序,节省时间
- 写操作的时候会打开大量文件
- 类似于Hash Shuffle
8.2 UnsafeShuffleWriter
- 使用类似内存页储存序列化数据
- 数据写入后不再反序列化
- 只根据partition排序 Long Array
- 数据不移动
8.3 SortShuffleWriter
- 支持combine
- 需要combine时,使用PartitionedAppendOnlyMap,本质是个HashTable
- 不需要combine时PartitionedPairBuffer本质是个array
9. Reader实现
9.1 网络时序图
- 使用基于netty的网络通信框架
- 位置信息记录在MapOutputTracker中主要会发送两种类型的请求
- OpenBlocks请求
- Chunk请求或Stream请求
9.2 ShuffleBlockFetchlterator
- 区分local和remote节省网络消耗
- 防止OOM
- maxByteslnFlight
- maxReqslnFlight
- maxBlockslnFlightPerAddress
- maxReqSizeShuffleToMem
- maxAttemptsOnNettyOOM
9.3 External Shuffle Service
- ESS作为一个存在于每个节点上的agent为所有Shuffle Reader提供服务,从而优化了Spark作业的资源利用率,MapTask在运行结束后可以正常退出
10. Shuffle优化使用的技术
10.1 Zero Copy
- 不使用zero copy
- 使用sendfile
- 使用sendfile +DMA gather copy
10.2 Netty Zero Copy
- 可堆外内存,避免JVM堆内存到堆外内存的数据拷贝
- CompositeByteBuf、Unpooled.wrappedBuffer、ByteBuf.slice,可以合并、包装、切分数组,避免发生内存拷贝
- Netty使用FileRegion 实现文件传输,FileRegion底层封装了FileChannel#transferTo()方法,可以将文件缓冲区的数据直接传输到目标Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
11. 常见问题
- 数据存储在本地磁盘,没有备份
- IO并发:大量 RPC请求(M*R)
- IO吞吐:随机读、写放大(3X)
- GC频繁,影响NodeManager
12. shuffle优化
-
避免shuffle ——使用broadcast替代join
-
//传统的join操作会导致shuffle操作。 //因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。 val rdd3 = rdd1.join(rdd2) //Broadcast+map的join操作,不会导致shuffle操作。 //使用Broadcast将一个数据量较小的RDD作为广播变量。 val rdd2Data = rdd2.collect() val rdd2DataBroadcast = sc.broadcast(rdd2Data) //在rdd1.map算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。 //然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以进行join。 //此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String或Tuple)。 val rdd3 = rdd1.map(rdd2DataBroadcast...) //注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。 //因为每个Executor的内存中,都会驻留一份rdd2的全量数据。 复制代码 -
使用可以map-side预聚合的算子
12.1 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
12.2 Shuffle 倾斜优化
- 什么叫倾斜?有什么危害
- 作业运行时间变长
- Task OOM导致作业失败
- 解决倾斜方法举例
- 增大并发度
- AQE
四、 Push Shuffle
1. Push Shuffle的实现
- Facebook: cosco
- LinkedIn: magnet
- Uber: Zeus
- Alibaba: RSS
- Tencent: FireStorm
- Bytedance: CSs
- Spark3.2: push based shuffle
2. 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
3. Magnet可靠性
- 如果Map task输出的Block没有成功Push到magnet上,并且反复重试仍然失败,则reducetask直接从ESS上拉取原始block数据
- 如果magnet上的block因为重复或者冲突等原因,没有正常完成merge的过程,则reducetask直接拉取未完成merge的block
- 如果reduce拉取已经merge好的block失败,则会直接拉取merge前的原始block
- 本质上,magnet中维护了两份shuffle数据的副本
4. 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]
5. Cloud Shuffle Service写入流程
6. Cloud Shuffle Service 读取流程
7. 4Cloud Shuffle Service AQE
- 一个Partition会最终对应到多个Epoch file,每个EPoch目前设置是512MB
8. 实践案例- CSS优化
- XX业务小时级任务(1.2w cores)
- 混部队列2.5h ->混部队列+CSS 1.3h (50%提升)
五、 课程总结
- Shuffle概述
- 什么是shuffle,shuffle的基本流程
- 为什么shuffle对性能影响非常重要
- Shuffle算子
- 常见的shuffle算子
- 理解宽依赖和窄依赖,ShuffleDependency及其相关组件
- Shuffle过程
- Spark中shuffle实现的历史
- Spark中主流版本的shuffle写入和读取过程
- Push shuffle
- Magnet Push Shuffle的设计思路
- Cloud Shuffle Service的设计实现思路
引用参考
内容主要参考了魏中佳老师在「大数据 Shuffle 原理与实践」课程里所教授的内容,同时也参考了学员手册里第三节的内容,图片来自于老师的PPT,链接如下: