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

128 阅读7分钟

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

今天是大数据专场基础班的第六次课,主要内容是介绍大数据 Shuffle 的原理与实践,主要分为下面四个板块。

一、 shuffle概述

1. MapReduce概述

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

image.png

2. Map阶段

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

3. Shuffle阶段

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

4. Reduce过程

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

5. 为什么shuffle对性能非常重要

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

image.png

总结

image.png

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

二、 shuffle算子

1. Shuffle算子分类

  • Spark中会产生shuffle的算子大概可以分为4类

image.png

2. Shuffle算子应用

  • Spark原码中RDD的单元测试
  • Spark原码中PairRDDFunctions的单元测试
l val text = sc.textFile ("mvtextfile .txt") 
2 val counts = text
3   .flatMap(line => line.split(" "))
4   .map(word =>(word ,1) )
5   .reduceByKey(_+_)
6 counts.collect

3. Spark中对shuffle的抽象–宽依赖、窄依赖

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

image.png

4. 算子内部的依赖关系

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

5. Shuffle Dependency构造

  • 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

5.1 Partitioner

  • 用来将record映射到具体的partition的方法
    • 接口
      • numberPartitions
      • getPartition

5.2 Aggregator

  • 在map侧合并部分record的函数
    • 接口
      • createCombiner: 只有一个value的时候初始化的方法
      • mergeValue: 合并一个value到Aggregator中
      • mergeCombiners: 合并两个Aggregator

三、 shuffle过程

1. Hash Shuffle-写数据

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

image.png

2. Hash Shuffle -写数据优化

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

image.png

3. Sort shuffle :写数据

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

image.png

4. Shuffle -读数据

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

image.png

5. Shuffle过程的触发流程

image.png

6. Shuffle Handle的创建

  • Register Shuffle时做的最重要的事情是根据不同条件创建不同的shuffle Handle

image.png

7. Shuffle Handle 与Shuffle Writer的对应关系

image.png

8. Writer 实现

8.1 BypassMergeShuffleWriter

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

image.png

8.2 UnsafeShuffleWriter

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

image.png

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

image.png

8.3 SortShuffleWriter

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

image.png

9. Reader实现

9.1 网络时序图

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

image.png

9.2 ShuffleBlockFetchlterator

  • 区分local和remote节省网络消耗
  • 防止OOM
    • maxByteslnFlight
    • maxReqslnFlight
    • maxBlockslnFlightPerAddress
    • maxReqSizeShuffleToMem
    • maxAttemptsOnNettyOOM

image.png

9.3 External Shuffle Service

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

image.png

10. Shuffle优化使用的技术

10.1 Zero Copy

  • 不使用zero copy

image.png

  • 使用sendfile

image.png

  • 使用sendfile +DMA gather copy

image.png

10.2 Netty Zero Copy

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

11. 常见问题

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

12. shuffle优化

  • 避免shuffle ——使用broadcast替代join

  • //传统的join操作会导致shuffle操作。
    //因为两个RDD中,相同的key都需要通过网络拉取到一个节点上,由一个task进行join操作。
    val rdd3 = rdd1.join(rdd2)
    
    //Broadcast+map的join操作,不会导致shuffle操作。
    //使用Broadcast将一个数据量较小的RDD作为广播变量。
    val rdd2Data = rdd2.collect()
    val rdd2DataBroadcast = sc.broadcast(rdd2Data)
    
    //在rdd1.map算子中,可以从rdd2DataBroadcast中,获取rdd2的所有数据。
    //然后进行遍历,如果发现rdd2中某条数据的key与rdd1的当前数据的key是相同的,那么就判定可以进行join。
    //此时就可以根据自己需要的方式,将rdd1当前数据与rdd2中可以连接的数据,拼接在一起(String或Tuple)。
    val rdd3 = rdd1.map(rdd2DataBroadcast...)
    
    //注意,以上操作,建议仅仅在rdd2的数据量比较少(比如几百M,或者一两G)的情况下使用。
    //因为每个Executor的内存中,都会驻留一份rdd2的全量数据。
    复制代码
    
  • 使用可以map-side预聚合的算子

12.1 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

12.2 Shuffle 倾斜优化

  • 什么叫倾斜?有什么危害
    • 作业运行时间变长
    • Task OOM导致作业失败
  • 解决倾斜方法举例
    • 增大并发度
    • AQE

四、 Push Shuffle

1. Push Shuffle的实现

  • Facebook: cosco
  • LinkedIn: magnet
  • Uber: Zeus
  • Alibaba: RSS
  • Tencent: FireStorm
  • Bytedance: CSs
  • Spark3.2: push based shuffle

2. Magnet 实现原理

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

image.png

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

image.png

3. Magnet可靠性

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

4. 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.png

5. Cloud Shuffle Service写入流程

image.png

6. Cloud Shuffle Service 读取流程

image.png

7. 4Cloud Shuffle Service AQE

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

image.png

8. 实践案例- CSS优化

  • XX业务小时级任务(1.2w cores)
  • 混部队列2.5h ->混部队列+CSS 1.3h (50%提升)

image.png

五、 课程总结

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

引用参考

内容主要参考了魏中佳老师在「大数据 Shuffle 原理与实践」课程里所教授的内容,同时也参考了学员手册里第三节的内容,图片来自于老师的PPT,链接如下:

  1. 【大数据专场 学习资料二】第四届字节跳动青训营 - 掘金 (juejin.cn)
  2. ​‌‍‬⁠⁡⁢⁣⁤大数据 Shuffle 原理与实践 - 魏中佳 - ppt.pptx - 飞书文档 (feishu.cn)