这是我参与「第四届青训营 」笔记创作活动的第4天
shuffle概述
MapReduce 的三个阶段
- map阶段:在单机上进行的针对一小块数据的计算过程
- shuffle阶段:在map阶段的基础上,进行数据移动,为后续reduce阶段做准备
- reduce阶段:对移动后的数据进行处理,依然是在单机上处理一小块数据
为什么shuffle对性能非常重要?
- M*R次网络连接
- 大量数据移动
- 数据移动和计算的过程中,存在丢失风险
- 可能存在大量排序操作
- 大量的数据序列化、反序列化操作
- 数据压缩(如果数据量非常大,存储中可能涉及)
shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大。目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle是支撑spark进行大规模复杂数据处理的基石。
shuffle算子
spark中会产生shuffle的算子分为4类
- repartition (coalesce, repartition)
- ByKey (groupByKey, reduceByKey, aggregateByKey, etc.)
- join (join, subtract, cogroup, etc.)
- distinct
算子内部的依赖关系 ShuffleDependency
- CoGroupedRDD
- ShuffledRDD
ShuffleDependency 的构造
- 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
shuffle过程
Hash Shuffle
- 写数据:每个partition会映射到一个独立的文件。每个Map Task都会给每个partition创建一个buffer,写满后flush到磁盘
- 写数据优化:每个partition会映射到一个文件片段
- 优点:不需要排序
- 缺点:打开、创建的文件过多
Sort Shuffle
- 每个task生成一个包含所有 partition 数据的文件
- 优点:打开的文件少、支持map-side combine
- 缺点:需要排序
Shuffle读数据:每个reduce task分别获取所有map task生成的属于自己的片段
Shuffle过程的触发流程:Collect Action -> Submit Job -> Get Dependencies -> RegisterShuffle
- Register Shuffle时做的最重要的事情是根据不同条件创建不同的 Shuffle Handle
- 三种ShuffleHandle对应了三种不同的ShuffleWriter的实现
- BypassMergeSortShuffleWriter:BypassMergeSortShuffleHandle
- UnsafeShuffleWriter:SerializedShuffleHandle
- SortSHuffleWriter:BaseShuffleHandle
Push based shuffle
spark3.2中引入的
之前Shuffle的问题
- 数据存储在本地磁盘,没有备份
- IO并发:大量 RPC 请求(M*R)
- IO吞吐:随机读、写放大(3X)
- GC频繁,影响NodeManager
为什么需要push shuffle?
Avg IO size 太小,造成了大量的随机IO,严重影响磁盘的吞吐。M*R次读请求,造成大量的网络连接,影响稳定性