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

87 阅读7分钟

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

06 大数据 shuffle 原理与实践

今天学习的内容是Shuffle,首先通过MR框架引出Shuffle的概念,然后介绍了shuffle的算子,shuffle的过程,以及提出了shuffle的几种优化方案,最后介绍了PushShuffle,列举了一些公司的优化方案,重点讲解了magnet和CSS的优化思路,详细内容如下:

1 Shuffle概述

1.1MapReduce

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

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

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

02

为什么shuffle 对性能非常重要

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

**总结思考:**本部分通过MR框架来引出Shuffle概念,数据shuffle 表示了不同分区数据交换的过程,shuffle的基本过程;

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

2 Shuffle算子

2.1 Shuffle算子

  • repartition:coalesce、repartition
  • ByKey:groupByKey、reduceByKey、aggregateByKey、combineByKey、sortByKeysortBy
  • Join:cogroup、join
  • Distinct :distinct

算子应用

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

reduceByKey产生了Shuffle,它需要把相同的 key 都放到一起,然后由一台机器去处理。

2.2 Spark中对shuffle的抽象

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

算子内部依赖数如下:

ShuffleDependency

  • coGroupedRDD
    • ogroup
      • fullOuterJoin、rightOuterJoin、leftOuterJoin
      • join
  • ShuffledRDD
    • combineByKeyWithClassTag
      • combineByKey
      • reduceByKey .
    • Coalesce
    • sortByKey
      • sortBy

2.3 Shuffle Dependency 构造

  • 创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle相关的信息
  • 构造函数
    • 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.

Partitioner

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

Aggregator

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

**总结:**本部分介绍了Shuffle的3种算子,举了一个reduceByKey例子介绍Shuffle的产生,然后介绍了Spark中shuffle的依赖关系,然后讲解了Shuffle Dependency及其相关组件,其中重点介绍了Partitioner和Aggregator函数及其接口

3 Shuffle 过程

3.1 Shuffle-写数据

1 Hash Shuffle :写数据

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

  • **问题:**数据太多会导致生成的文件太多,同时也会打开太多的文件,面临OM的问题

  • **优化方案:**每个partition会映射到一个文件片段

  • **优化后问题:**并没有从根源解决问题,同样会打开R个文件也会面临OM的问题,所以慢慢的从Hash Shuffle过渡到Sort Shuffle

2 Sort shuffle :写数据

  • 每个task生成一个包含所有partiton数据的文件
  • 优点:打开的文件少、支持map-side combine
  • 缺点:需要排序

TungstenSortShuffle

  • 优点:更快的排序效率,更高的内存利用效率
  • 缺点:不支持map-side combine

3.2 Shuffle -读数据

  • 每个reduce task分别获取所有map task生成的属于自己的片段
  • Hash Shuffle与Sort Shuffle虽然打开文件数目不一样,但逻辑基本一样

3.3 Shuffle过程的触发流程

Collect Action => SubmitJob => GetDependencies => RegisterShuffle

image-20220802134411837

3.4 Shuffle Handle的创建

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

  • 由action算子触发DAG Scheduler进行shuffle register
  • Shuffle Register会根据不同的条件决定注册不同的ShuffleHandle

具体的条件判断流程如下图

03

3.5 三种ShuffleHandle对应了三种不同的ShuffleWriter的实现

  • img
  • BypassMergeSortShuffleWriter:HashShuffle
  • UnsafeShuffleWriter:TunstonShuffle
  • SortSHuffleWriter:SortShuffle

3.6 Writer 实现

  • BypassMergeShuffleWriter

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

    • 适用于数量较大的partitioner
    • 只根据partition 排序 Long Array
    • 数据不移动
  • SortShuffleWriter

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

3.7 Reader 实现

3.7.1 网络请求流程

img

使用netty作为网络框架提供网络服务,并接受reducetask的fetch请求

首先发起openBlocks请求获得streamId,然后再处理stream或者chunk请求

3.7.2 ShuffleBlockFetchIterator

  • 区分local和remote节省网络消耗
  • 防止OOM
    • maxBytesInFlight//限制数获取的数据块的大小
    • maxReqsInFlight//限制正在获取的这个数请求的数量
    • maxBlocksInFlightPerAddress
    • maxReqSizeShuffleToMem
    • maxAttemptsOnNettyOOM//限制 OM 的次数的

3.7.3 External Shuffle Service

1

  • ESS作为一个存在于每个节点上的agent为所有Shuffle Reader提供服务,从而优化了Spark作业的资源利用率,MapTask在运行结束后可以正常退出
  • 为了解决Executor为了服务数据的fetch请求导致无法退出问题,我们在每个节点上部署一个External Shuffle Service,这样产生数据的Executor在不需要继续处理任务时,可以随意退出。

3.8 Shuffle优化

3.8.1避免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预聚合的算子

3.8.2 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.8.3 Shuffle 倾斜优化

  • 什么叫倾斜?有什么危害

倾斜影响:作业运行时间变长;Task OOM导致作业失败

  • 解决倾斜方法举例
    • 增大并发度//优点:足够简单 缺点:只缓解、不根治
    • AQE(Spark AQE Skew Joir)//AQE根据shuffle文件统计数据自动检测倾斜数据,将那些倾斜的分区打散成小的子分区,然后各自进行join。

3.8.4 Zero Copy

DMA(Direct Memory Access):直接存储器存取,是指外部设备不通过CPU而直接与系统内存交换数据的接口技术。 不使用zero copy => 使用sendfile => 使用sendfile +DMA gather copy

3.8.5 Netty Zero Copy

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

3.9 常见问题

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

4 Push Shuffle

  • 上一部分所讲的shuffle过程存在哪些问题?

    • 数据存储在本地磁盘,没有备份
    • IO 并发:大量 RPC 请求(M*R)
    • IO 吞吐:随机读、写放大(3X)
    • GC 频繁,影响 NodeManager
  • 为了优化该问题,有很多公司都做了思路相近的优化,push shuffle

4.1 Magnet主要流程

  • img
  • img

主要为边写边push的模式,在原有的shuffle基础上尝试push聚合数据,但并不强制完成,读取时优先读取push聚合的结果,对于没有来得及完成聚合或者聚合失败的情况,则fallback到原模式。

4.2 Cloud Shuffle Service架构

  • img

  • Zookeeper WorkerList [服务发现]

  • CSS Worker [Partitions / Disk | Hdfs]

  • Spark Driver [集成启动 CSS Master]

  • CSS Master [Shuffle 规划 / 统计]

  • CSS ShuffleClient [Write / Read]

  • Spark Executor [Mapper + Reducer]

  • Cloud Shuffle Service 读写流程

    • img
  • Cloud Shuffle Service 支持AQE

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

参考链接:juejin.cn/post/712390…