Shuffle原理|青训营笔记

125 阅读3分钟

这是我参与「第四届青训营 」笔记创作活动的第6天

Shuffle原理

大量的网络连接和数据移动,数据的丢失风险,涉及排序、序列化或反序列化,数据压缩让shuffle对于性能的要求十分的高。

shuffle算子

分类

一般有3类

  • 改变分区
  • 聚合ByKey
  • join
  • 去重Distinct(特殊的ByKey)

在Spark中Shuffle被抽象为宽依赖和窄依赖


算子内部的依赖关系

graph LR;
A(ShuffleDependency)
A1(CoGroupedRDD)
A11(CoGroup)
A111(fullOuter, JoinrightOuterJoin, leftOuterJoin)
A112(join)
A2(ShuffledRDD)
A21(combineByKeyWithClassTag)
A211(combineByKey)
A212(reduceByKey)
A22(Coalesce)
A23(sortByKey)
A231(sortBy)

A --> A1
A --> A2

A1 --> A11
A2 --> A21
A2 --> A22
A2 --> A23

A11 --> A111
A11 --> A112
A21 --> A211
A21 --> A212
A23 --> A231

shuffle 过程

Hash Shuffle

写数据时,每个partition会映射到一个独立文件(运行过程中生成和打开的文件会很多)

优化: 每个partition会映射到一个文件片段(老问题仍然存在)


Sort Shuffle

写数据时,每个task生成一个包含partition数据的文件(sort会很消耗cpu)

读数据是,每个reduce task分别获取每个map task生成的属于自己的片段


Shuffle过程的触发流程

graph LR;
A(Conllect Action)
B(Submit Job)
C(Get Dependences)
D(Register Shuffle)
A --> B --> C --> D

Register Shuffle时做的最重要的事情就是 根据不同的条件创建不同的Shuffle Handle ,不同的Shuffle Handle对应不同的Shuffle Writer

Writer实现

  • BypassMergeShuffleWriter: 不排序,写操作产生大量文件,类似Hash Shuffle
  • UnsafeShuffleWriter: 只根据partition排序Long Array,数据不移动,使用堆外内存
  • SortShuffleWriter: 支持combine (需要combine时,使用PartitionedAppendOnlyMap本质是HashTable,不需要combine时,PartitionedPairBuffer本质是Array) ,对partition,keyvalue排序,使用堆内内存

Reader实现 - ShuffleBlockFetcherIterator

BlockStoreShuffleReader.read()读取数据有五步:

  1. 初始化ShuffleBlockFetcherIterator,负责从executor中获取 shuffle 块
  2. 将shuffle 块反序列化为record迭代器
  3. reduce端聚合数据
  4. reduce端排序数据
  5. 返回结果集迭代器


Shuffle 优化

Zero Copy

DMA(Direct Memory Access): 直接存储器存取,指外部设备不通过CPU,直接和系统内存交换数据的接口技术。

zero copy就像DMA一样通过减少CPU的参与来优化Shuffle


Netty Zero Copy

  • 可堆外内存:避免JVM堆内存到堆外存的数据拷贝
  • 一些对象可以合并、包装、切分数组,避免发生内存拷贝
  • Netty使用FileRegion实现文件传输,FileRegion底层封装了 FileChannel#transferto() 方法,可以将文件缓冲区的数据直接传输到目标Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝。

使用broadcast代替join

传统的Join操作是会导致Shuffle的,若某一个RDD较小可以使用broadcast代替join避免Shuffle操作。


map-side预聚合

例如进行wordcount,在map之后进行一次count,而之后的reduce则是进行sum操作,这样可以压缩Shuffle的数据量。


参数优化

  • 默认的并发度: spark.default.parallelism && spark.sql.shuffle.partitions
  • 去掉空的partition: spark.hadoopRDD.ignoreEmptySplits
  • 读取hdfs数据时可做合并: spark.hadoop.mapreduce.input.fileinputformat.split.minszie
  • 读取数据时规定task应处理的数据量: spark.sql.file.maxPartitionBytes
  • 动态调整reduce的partition数量: spark.sql.adaptive.enabled && spark.sql.adaptive.shuffle.targetPostShuffleInputSize

倾斜优化

shuffle倾斜指的是源数据的原因导致reduce收到的数据严重倾斜于某一个reduce task。这样会影响作业的运行时间,甚至是task OOM导致作业失败。

常见的处理方式:

  • 提高并行度
  • Spark3.0 AQE