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

204 阅读6分钟

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

本节课程目录:

  1. Shuffle 概述
  2. Shuffle 算子
  3. Shuffle 过程
  4. Push Shuffle

1. Shuffle 概述

1.1 MapReduce 概述

  • 2004年谷歌发布了《MapReduce:Simplified Data Processing on Large Clusters》论文
  • 在开源实现的 MapReduce 中,存在 Map、Shuffle、Reduce 三个阶段

image.jpeg

1.1.1 Map 阶段

是在单机上进行的针对一小块数据的计算过程

image.gif

1.1.2 Shuffle 阶段

在 map 阶段的基础上,进行数据移动,为后续的 reduce 阶段做准备

image-2.gif

1.1.3 Reduce 阶段

对移动后的数据进行处理,依然是在单机上处理一小份数据

image-3.gif

1.4 为什么 Shuffle 对性能非常重要

  • M * R 次网络连接
  • 大量的数据移动
  • 数据有丢失风险
  • 可能存在大量的排序操作
  • 大量的数据序列化、反序列化操作
  • 数据压缩

2. Shuffle 算子

2.1 Shuffle 算子分类

截屏2022-08-01 19.02.47.png

  • repartition 重新改变分区
  • ByKey 把指定 Key - Value 对聚合到一起进行计算
  • join 按照某种条件把数据放到一起进行计算
  • Distinct 特殊的 ByKey 操作

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

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

image.png

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

2.2.1 Shuffle Dependency 构造

  • A single key - value pair RDD 一个kv对形式的 RDD
  • Partitioner 给定 Key 所对应的分区
  • Serializer 把对象映射成二进制的数据流或把二进制的数据映射成对象
  • Optional Key ordering 排序 Key
  • Optional Aggregator 详见下
  • mapSideCombine flag which is disabled by default

Partitioner(用来将 record 映射到具体的partition的方法)

  • 两个接口
    • numberPartitions
    • getPartition image-2.png
  • 经典实现
    • HashPartitioner image-3.png

Aggregator(在 map 阶段合并部分 record 的函数)

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

3. Shuffle 过程

  • Shuffle 实现的发展历程
    • Spark 0.8 及以前 Hash Based Shuffle
    • Spark 0.8.1 为 Hash Based Shuffle 引入 File Consolidation 机制
    • Spark 0.9 引入 ExternalAppendOnlyMap
    • Spark 1.1 引入 Sort Based Shuffle,但默认仍为 Hash Based Shuffle
    • Spark 1.2 默认的 Shuffle 方式改为 Sort Based Shuffle
    • Spark 1.4 引入 Tungsten - Sort Based Shuffle
    • Spark 1.6 Tungsten - Sort Based Shuffle 并入 Sort Based Shuffle
    • Spark 2.0 Hash Based Shuffle 退出历史舞台

3.1 Hash Shuffle - 写数据

  • 不需要排序
  • 每个 partition 会映射到一个独立的文件。
    • 随着作业的规模增大,打开创建的文件数量会非常多,严重消耗资源。

image-4.png

  • 优化后,每个 partition 会映射到一个文件片段。
    • 实际上打开的文件数量与优化前相同。

image-5.png

3.2 Sort Shuffle - 写数据

  • 需要排序
  • 每个 task 生成一个包含所有 partition 数据的文件。

image-6.png

3.3 Shuffle - 读数据

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

image-7.png

3.4 Shuffle 过程的触发流程

graph TD
Collect-Action --> SubmitJob
 --> GetDependencies
 --> RegisterShuffle

3.5 Shuffle Handle 的创建

最重要的事情是根据不同条件创建不同的 shuffle Handle

image-2.jpeg

3.6 Shuffle Handle 与 Shuffle Writer 的对应关系

image-3.jpeg

3.7 Writer 实现

BypassMergeShuffleWriter

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

image-4.jpeg

UnsafeShuffleWriter

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

image-5.jpeg

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

image-6.jpeg

SortShuffleWriter

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

image-7.jpeg

3.8 Reader 实现

网络时序图

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

image-8.png

ShuffleBlockFetchIterator

  • 区分 local 和 remote 节省网络消耗
  • 防止 OOM
    • maxBytesInFlight
    • maxReqsInFlight
    • maxBlocksInFlightPerAddress
    • maxReqSizeShuffleToMem
    • maxAttemptsOnNettyOOM

image-9.png

External Shuffle Service

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

image-10.png

3.9 Shuffle 优化使用的技术

Zero Copy

  • 不使用 zero copy image-4.gif

  • 使用 sendfile image-5.gif

  • 使用 sendfile + DMA gather copy image-6.gif

Netty Zero Copy

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

3.10 常见问题

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

3.11 Shuffle 优化

  • 避免 shuffle
    • 使用 broadcost 替代 join image-11.png
    • 使用可以 map-side 预聚合的算子 image-8.jpeg image-9.jpeg

3.12 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

3.13 Shuffle 倾斜优化

  • 倾斜影响

    • 作业运行时间变长
    • Task OOM 导致作业失败
  • 解决办法

    • 提高并行度
      • 优点:足够简单
      • 缺点:只缓解,不根治
  • Spark AQE Skew Join

image-12.png

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

image-13.png

3.14 案例 - 参数优化

  • ad_show
    • number of files read:840,042
    • number of total tasker:5,553
    • size of files read:203.3 TB
    • number of output rows:128,676,054,598

image-14.png

3.15 参数调整

  • spark.sql.adaptive.shuffle.targetPostShuffleInputSize: 64M -> 512M
  • spark.sql.files.maxPartitionBytes: 1G -> 40G
  • 通过参数调整增大了 Chunk Size,减小了 Shuffle 过程中IOPS,避免了长时间的 Blocked Time
  • 在增大 Map Task 的数据处理量后,由于该算子在 Map Side Aggregation,反而减少了整体的 Shuffle 数据量

4. Push Shuffle

4.1 为什么需要 Push Shuffle

  • Avg IO size 太小,造成了大量的随机 IO,严重影响磁盘的吞吐
  • M * R 次读请求,造成大量的网络连接,影响稳定性

image-11.jpeg

4.2 Push Shuffle 的实现

4.3 Magnet 实现原理

  • Spark driver 组件用来协调整体的 shuffle 操作
  • Map task 的 shuffle writer 过程完成后,增加了一个额外的操作 push - merge,将数据复制一份推到远程 shuffle 服务上
  • magnet shuffle service 是一个 ESS 的 plus版。将隶属于同一个 shuffle partition 的 block,会在远程传输到 magent 后被 merge 到一个文件中
  • reduce 任务从 magnet shuffle service 接收合并好的 shuffle 数据

image-15.png

  • bitmap:存储已 merge 的 mapper id,防止重复 merge
  • position offset:如果本次 block 没有正常 merge,可以恢复到上一个 block 的位置
  • currentMapId:标识当前正在 append 的 block,保证不同 mapper 的 block 能依次 append

image-12.jpeg

4.4 Magnet 可靠性

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

4.5 Cloud Shuffle Service 思想

截屏2022-08-01 20.36.52.png

4.6 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]

image-16.png

4.6.1 Cloud Shuffle Service 写入流程(图左)

4.6.2 Cloud Shuffle Service 读取流程(图右)

image-17.png

4.6.3 Cloud Shuffle Service AQE

一个 Partition 会最终对应到多个 Epoch file,每个 Epoch 目前设置是 512 MB

  • 在聚合文件时主动将文件切分为若干块,当触发AQE时,按照已经切分好的文件块进行拆分

1.png