大数据 Shuffle 原理与实践|青训营笔记

85 阅读7分钟

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

大数据 Shuffle 原理与实践

1. Shuffle概述

经典shuffle过程:

1.jpg

Map、Shuffle、Reduce三个阶段:

  • Map阶段,是在单机上进行的针对一小块数据的计算过程,就是分类

  • Shuffle 阶段,在map阶段的基础上,进行数据移动,为后续的reduce阶段做准备,就是shuffle将同类型的数据进行合并

  • Reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据,就是对Shuffle合并后的数据进行sum值

Why Shuffle对性能非常重要

  • M*R次网络连接
  • 大量的数据移动(M*R次数据移动)
  • 数据丢失风险(移动和计算的过程中,有丢失的风险,面临数据重算)
  • 可能存在大量的排序操作
  • 大量的数据序列化、反序列化操作(会消耗大量cpu)
  • 数据压缩(在存储大量数据过程中,压缩与解压缩也会占用大量CPU)

总结:

MapReduce->Spark-> Spark3.2

数据shuffle表示了不同分区数据交换的过程,不同的shuffle策略性能差异较大。目前在各个引擎中shuffle都是优化的重点,在spark框架中,shuffle是支撑spark进行大规模复杂数据处理的基石

2. Shuffle算子

Shuffle算子分类

常见的触发shuffle的算子:

  • repartition:重新改变分区
  • ByKey:把Key聚合到一起
  • Join:没有在一起的数据,放在一起
  • Distinct:特殊ByKey

6614f2a03859a10addaabf488dfbd81.png

Shuffle算子应用

Spark源码中RDD的单元测试

Spark源码中PairRDDFunctions的单元测试

val text = sc.textFile("mytextfile.txt")
val counts = text
  .flatMap(line => line.split(" "))
  .map(word => (word,1))
  .reduceByKey(_+_)
counts.collect

Spark中对shuffle的抽象 - 宽依赖、窄依赖

窄依赖: 父RDD的每个分片至多被子RDD中的一个分片所依赖

宽依赖: 父RDD中的分片可能被子RDD中的多个分片所依赖

b2b0495cee57114d6fbc90e4bbff73f.png

算子内部依赖:

  • ShuffleDependency
    • CoGroupedRDD
      • Cogroup
        • fullOuterJoin、rightOuterJoin、leftOuterJoin
        • join
  • ShuffledRDD
    • combineByKeyWithClassTag
      • combineByKey
      • reduceByKey
    • Coalesce
    • sortByKey
      • sortBy

Shuffle Dependency

创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle相关的信息

Partitioner

负责把一个Key映射成一个数字,代表了某一个具体分区

  • 两个接口:

    • numberPartitions
    • getPartition
  • 经典实现: HashPartitioner

Aggregator

进行Shuffle时非常重要的性能优化器

  • createCombiner:只有一个value的时候初始化的方法
  • mergeValue:合并一个value到Aggregator中
  • mergeCombiners:合并两个Aggregator

3. Shuffle过程

Hash Shuffle - 写数据

每个 partition 会映射到一个独立的文件

8ee741b523a7bcf8a618e67d743564f.png

缺点:生成文件太多,面临OM问题

Hash Shuffle - 写数据优化

每个 partition 会映射到一个文件片段

894913e2fe0954e6dd16d56cacbcb52.png

最后得到C*R个文件,C是CPU核数

缺点:依然会有OM问题

Sort shuffle:写数据

每个 task 生成一个包含所有 partiton 数据的文件

ae95002d7f0d2271fd8e672952e47d0.png

Shuffle - 读数据

每个 reduce task 分别获取所有 map task 生成的属于自己的片段

8a9bd36e0af25d2d5cd29fb2a3ca3e7.png

Shuffle过程的触发流程示例

Collect Action->SubmitJob->GetDependencies->RegisterShuffle

Shuffle Handle的创建

Shuffle Register会根据不同的条件决定注册不同的ShuffleHandle

1.jpg

Shuffle Handle与Shuffle Writer的对应关系

BypassMergeSortShuffleWriter:HashShuffle UnsafeShuffleWriter:TunstonShuffle SortSHuffleWriter:SortShuffle

1.jpg

Writer实现 - BypassMergeShuffleWriter

  • 不需要排序,节省时间
  • 写操作的时候会打开大量文件
  • 类似于Hash Shuffle

5297359a796e86f2c36ed9b92b2e7a2.png

Writer实现- UnsafeShuffleWriter(用了对外的内存)

  • 使用类似内存页储存序列化数据
  • 数据写入后不再反序列化

d536cd29a73ddf0fad173db117a5466.png

  • 只根据 partition 排序 Long Array
  • 数据不移动

1b375cb2257f9eead59f8ef20bc76de.png

Writer实现- SortShuffleWriter(不使用对外内存)

  • 支持 combine
  • 需要 combine 时,使用 PartitionedAppendOnlyMap ,本质是个 HashTable
  • 不需要 combine 时 PartitionedPairBuffer 本质是个 array

f06d4e95eff60fcfde4aab0680103de.png

Reader实现 - 网络时序图

  • 使用基于 netty 的网络通信框架
  • 位置信息记录在 MapOutputTracker 中主要会发送两种类型的请求:
    • OpenBlocks 请求
    • Chunk 请求或 Stream 请求

1.jpg

Reader 实现 - ShuffleBlockFetchIterator

  • 区分 local 和 remote 节省网络消耗
  • 防止 OOM
    • maxBytesInFlight 限制数据块大小
    • maxReqsInFlight 限制请求数量
    • maxBlocksInFlightPerAddress 限制每一个地址上的Block数量
    • maxReqSizeShuffleToMem 最大请求Size
    • maxAttemptsOnNettyOOM 请求会造成对外内存OOM,控制OOM次数

Read 实现 - External Shuffle Service

ESS 作为一个存在于每个节点上的 agent 为所有 Shuffle Reader 提供服务,从而优化了 Spark 作业的资源利用率, MapTask在运行结束后可以正常退出

535a2e7c40a61a78acaa09b0c52c776.png

Shuffle优化使用的技术Zero Copy

不使用Zero Copy: 654ae6b7dc4aca47de74aeef613e799.png

使用sendfile b10229e5c0ebacd58b6eaf392bde1ce.png

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

使用sendfile+DMA gather copy 7a67bdc9f8de033f71d553387c76bc0.png

Shuffle优化使用的技术: Netty Zero Copy

  • 可堆外内存,避兔 JVM 堆内存到堆外内存的数据拷贝
  • CompositeByteBuf、Unpooled.wrappedBufer、ByteBuf.slice,可以合并、包装、切分数组,避免发生内存拷贝
  • Netty 使用 FileRegion 实现文件传输, FileRegion 底层封装了 FileChannel#transferTo0方法,可以将文件缓冲区的数据直接传输到目标 Channel ,避兔内核缓冲区和用户态缓冲区之间的数据拷贝

常见问题

  • 数据存储在本地磁盘,没有备份
  • IO 并发:大量 RPC 请求( M * R )
  • IO 吞吐:随机读、写放大(3X)
  • GC 频繁,影响 NodeManager

shuffle优化

  • 避兔 shuffle
    • 使用 broadcast 替 join
  • 使用可以map-side预聚合的算子

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 .targetPostShufflelnputSize
  • spark.reducer.maxSizelnFlight
  • spark.reducer.maxReqslnFlight
  • spark.reducer.maxBlockslnFlightPerAddress

Shuffle 倾斜优化

倾斜影响:

  • 作业运行时间变长
  • Task OOM 导致作业失败

常见倾斜处理办法

提高并行度:

  • 优点:足够简单
  • 缺点:只缓解、不根治

Spark AQE Skew Join

AQE 根据 shuffle 文件统计数据自动检测倾斜数据,将那些倾斜的分区打散成小的子分区,然后各自进 行 join

0bf54cc03d16da5ed98e47cac3d108c.png

参数调整

  • ad show
  • number of files read :840,042
  • number of total tasks :5,553
  • size of files read :203.3 TiB
  • number of output rows :128,676,054,598

4. Push Shuffle

为什么需要Push Shuffle ?

  • Avg IO size太小,造成了大量的随机IO,严重影响磁盘的吞吐

  • M*R次读请求,造成大量的网络连接,影响稳定性

Push Shuffle的实现

  1. Facebook:cosco
  2. Linkdin:magnet
  3. Uber:Zeus
  4. Alibaba:RSS
  5. Tencent:FireStorm
  6. Bytedance:CSS
  7. Spark3.2:push based shuffle

Magnet

1. 实现原理

  • 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

1.jpg

1.jpg

2. 可靠性

  • 如果Map task输出的Block没有成功Push到magnet上,并且反复重试仍然失败,则reduce task直接从ESS上拉取原始block数据
  • 如果magnet上的block因为重复或者冲突等原因,没有正常完成merge的过程,则reduce task直接拉取未完成merge的block
  • 如果reduce拉取已经merge好的block失败,则会直接拉取merge前的原始block本质上,magnet中维护了两份shuffle数据的副本

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)

1.jpg

课程总结

  1. Shuffle 概述
    • 什么是 shufle , shufle 的基本流程
    • 为什么 shuffle 对性能景响非常重要
  2. Shufle 算子
    • 常见的 shufle 算子
    • 理解宽依赖和窄依赖, ShuffleDependency 及其相关组件
  3. Shuffle 过程
    • Spark 中 shufle 实现的历史
    • Spark 中主流版本的shuffle 写入和读取过程
  4. Push shuffle
    • Magnet Push Shuffle 的设计思路
    • Cloud Shufle Service 的设计实现思路