大数据Shuffle原理与实践 | 青训营笔记
这是我参与「第四届青训营 」笔记创作活动的第6天
今天学习了课程《大数据Shuffle原理与实践》,学习的内容主要分为以下几个部分:
- Shuffle概述
- Shuffle算子
- Shuffle过程
- Push Shuffle
一、Shuffle概述
在开源实现的MapReduce中,存在Map、Shuffle、Reduce三个阶段。
- Map阶段,是在单机上进行的针对一小块数据的计算过程。
- Shuffle阶段,在Map阶段的基础上,进行数据移动,为后续的reduce阶段做准备。
- Reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据。
为什么Shuffle对性能非常重要呢?
数据Shuffle表示了不同分区数据交换的过程,不同的Shuffle策略性能差异较大。目前在各个引擎中shuffle都是优化的重点,在Spark框架中,Shuffle是支撑spark进行大规模复杂数据处理的基石。
二、Shuffle算子
2.1 Shuffle算子的分类
| repartition | ByKey | join | Distinct |
|---|---|---|---|
| coalesce | groupByKey | cogroup | distinct |
| repartition | reduceByKey | join | |
| aggragateByKey | leftOuterJoin | ||
| combineByKey | insersection | ||
| sortByKey | subtract | ||
| sortBy | subtractByKey |
算子使用例子:
val text = sc.textFile("mytextfile.txt")
val counts = text
.flatMap(line => line.split(" "))
.map(word => (word,1))
.reduceByKey(_+_)
counts.collect
2.2 Spark中对Shuffle的抽象-宽依赖、窄依赖
- 窄依赖:父RDD的每个分片至多被子RDD中的一个分片所依赖。
- 宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖。
算子内部的依赖关系:
-
ShuffleDependency
-
CoGroupedRDD
-
Cogroup
- fullOuterJoin、rightOuterJoin、leftOuterJoin
- join
-
-
ShuffledRDD
-
combineByKeyWithClassTag
- combineByKey
- reduceByKey
-
Coalesce
-
sortByKey
- sortBy
-
-
Shuffle Dependency 构造
-
Partitioner
-
两个接口
- numberPartitions
- getPartition
-
经典实现
- HashPartitioner
-
-
Aggregator
- createCombiner:只有一个value的时候初始化的方法
- mergeValue:合并一个Value到Aggregator中
- mergeCombiners:合并两个Aggregator
三、Shuffle过程
3.1 HashShuffle
写数据:每个partition会映射到一个独立的文件。
写数据优化:每个partition会映射到一个文件片段。
- 优点:不需要排序
- 缺点:打开或创建的文件过多
3.2 SortShuffle
写数据:每个task生成一个包含所有partition数据的文件
- 优点:打开的文件少、支持map-side combine
- 缺点:需要排序
3.3 Shuffle读数据
每个reduce task 分别获取所有map task生成的属于自己的片段
3.4 Shuffle过程的触发流程
3.5 Register Shuffle
- 由action算子触发DAG Scheduler进行shuffle register
- Shuffle Register 会根据不同的条件决定注册不同的ShuffleHandle
3.6 三种ShuffleHandle对应了三种不同的ShuffleWriter的实现
-
BypassMergeSortShuffleWriter:HashShuffle
- 不需要排序,节省时间
- 写操作的时候会打开大量文件
- 类似于Hash Shuffle
-
UnsafeShuffleWriter:TunstonShuffle
- 使用类似内存页存储序列化数据
- 数据写入后不再反序列化
- 只根据partition排序
- 数据不移动
-
SortSHuffleWriter:SortShuffle
- 支持combine
- 需要combine时,使用PartitionAppendOnlyMap,本质是个HashTable
- 不需要combine时,PartitionPairBuffer本质是个Array
3.7 ShuffleReader网络请求流程
ShuffleReader 使用netty作为网络框架提供服务,并接受reducetask的fetch请求。首先发起openBlocks请求获得streamId,然后再处理stream或者chunk请求。
-
ShuffleBlockFetchIterator
- 区分loacl和remote节省网络消耗
- 防止OOM
-
External Shuffle Service
- 为了解决Executor为了服务数据的fetch请求导致无法退出问题,我们在每个节点上部署一个External Shuffle Service,这样产生数据的Executor在不需要继续处理任务时,可以随意退出。
3.8 Shuffle 优化
-
避免shuffle --- 使用broadcast替代join
-
使用map-side预聚合的算子
-
Shuffle参数优化
-
Shuffle倾斜优化:如:增大并发度,AQE
-
Netty 零拷贝
- 可堆外内存,避免JVM堆内存到堆外内存的数据拷贝。
- CompositeByteBuf、Unpooled.wrappedBuffer、 ByteBuf.slice ,可以合并、包装、切分数组,避免发生内存拷贝。
- Netty 使用 FileRegion 实现文件传输,FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝
四、Push Shuffle
Shuffle过程中可能遇到的问题:
- 数据存储在本地磁盘,没有备份
- IO并发:大量RPC请求
- IO吞吐:随机读、写放大
- GC频繁,影响NodeManager
为了优化上述问题,我们使用push shuffle优化
为什么需要Push Shuffle?
- Avg IO size太小,造成大量的随机IO,严重影响磁盘的吞吐。
- M*R 次读请求,造成大量的网络连接,影响稳定性。
Push Shuffle的实现
- Facebook:cosco
- LindedIn:magnet
- Uber:Zeus
- Alibaba:RSS
- Tencent:FireStorm
- Bytedance:Cloud Shuffle Service
- Spark3.2:push based shuffle
五、个人总结与思考
通过本课程,了解了Spark中shuffle主要的实现机制以及spark shuffle的底层实现原理,了解了Shuffle的优化,包括一些调优的策略,受益匪浅。shuffle原理涉及MapReduce、Spark等计算引擎,通过对Shuffle原理的理解以及优化,可以使得使用Spark等计算引擎处理大规模数据时更加高效。
\