第五节:大数据Shuffle原理与实践|青训营笔记

125 阅读5分钟

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

Shuffle概述

经典 Shuffle 过程

在开源实现的 MapReduce 中,存在三个阶段:Map 阶段、Shuffle 阶段、Reduce阶段。

  • Map阶段:在单机上进行的针对一小块数据 node 的计算过程。对乱序小块数据 node 进行排序

  • Shuffle阶段:在Map阶段基础上将不同 node 内数据拿出来总体排序

  • Reduce阶段:对移动后的数据处理,依然是单机上处理小份数据。

image.png

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

Shuffle算子

Spark中产生 Shuffle 的算子大概分成四类:

image.png

Spark 中对 Shuffle 的抽象

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

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

image.png

算子内部的依赖关系

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 过程

Spark 中 Shuffle 变迁过程

HashShuffle

写数据:每个 Partion 会映射到一个独立文件

image.png

写数据优化:每个Partion会映射到一个文件片段

image.png

  • 优点:不需要排序

  • 缺点:打开,创建的文件过多

SortShuffle 写数据:每个 task 生成一个包含所有 partion 数据的文件

image.png

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

image.png

  • 优点:打开的文件少、支持map-side combine

  • 缺点:需要排序

TungstenSortShuffle

  • 优点:更快的排序效率,更高的内存利用效率

  • 缺点:不支持map-side combine

Shuffle 过程的触发流程

graph LR
 Collect_Action --> SubmitJob --> GetDepencies --> RegisterShuffle

Register Shuffle

  • 由action算子触发DAG Scheduler进行shuffle register

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

Writer 的实现

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

  • BypassMergeSortShuffleWriter:类似 HashShuffle

  • UnsafeShuffleWriter:类似 TunstonShuffle

  • SortSHuffleWriter:类似 SortShuffle

Reader的实现

ShuffleReader网络请求流程

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

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

  • ShuffleBlockFetchIterator

    • 区分local和remote节省网络消耗

    • 防止OOM

      • maxBytesInFlight
      • maxReqsInFlight
      • maxBlocksInFlightPerAddress
      • maxReqSizeShuffleToMem
      • maxAttemptsOnNettyOOM

External Shuffle Service

为了解决Executor为了服务数据的fetch请求导致无法退出问题,我们在每个节点上部署一个External Shuffle Service,这样产生数据的Executor在不需要继续处理任务时,可以随意退出

Suffle 的优化

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的全量数据。
        复制代码

2、使用可以map-side预聚合的算子

3、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

4、Shuffle 倾斜优化

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

  • 解决倾斜方法举例

    • 增大并发度

    • AQE

5、零拷贝

  • sendfile+DMA gather copy

6、Netty 零拷贝

  • 可堆外内存,避免 JVM 堆内存到堆外内存的数据拷贝。

  • CompositeByteBuf 、 Unpooled.wrappedBuffer、 ByteBuf.slice ,可以合并、包装、切分数组,避免发生内存拷贝

  • Netty 使用 FileRegion 实现文件传输,FileRegion 底层封装了 FileChannel#transferTo() 方法,可以将文件缓冲区的数据直接传输到目标 Channel,避免内核缓冲区和用户态缓冲区之间的数据拷贝

Push Shuffle

常见问题

  • 数据存储在本地磁盘,没有备份

  • IO 并发:大量 RPC 请求(M*R)

  • IO 吞吐:随机读、写放大(3X)

  • GC 频繁,影响 NodeManager

各公司的优化方案

Magnet主要流程

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

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]

Cloud Shuffle Service 读写流程

Cloud Shuffle Service 支持AQE

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