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

153 阅读7分钟

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

大数据 Shuffle 原理与实践

一、Shuffle概述

1.1 MapReduce概述

image.png

Map阶段

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

image.gif

1.2 Shuffle阶段

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

1.3 Reduce过程

reduce阶段,对移动后的数据进行处理,依然是单机上处理的一小份数据 image (2).gif

1.4 为什么shuffle对性能非常重要?

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

总结

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

二、Shuffle算子

2.1 Shuffle算子分类

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

image.png

2.2 Shuffle算子应用

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

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

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

2.4 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.1 HashShuffle-写数据

  • 优点:不需要排序
  • 缺点:打开,创建的文件过多 每个partition会映射到一个独立的文件 image.png 每个partition会映射到一个文件片段 image.png

3.2 SortShuffle:写数据

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

3.3 Shuffle -读数据

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

image.png

  • TungstenSortShuffle

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

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

3.4 Shuffle过程的出发流程

image.png

3.5 Shuffle Handle的创建

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

3.6 Shuffle Handle 与Shuffle Writer 的对应关系

image.png

3.7 Writer实现-BypassMergeShuffleWriter

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

3.7.1 Writer实现-UnsafeShuffleWriter

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

image.png

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

3.7.2 Writer实现-SortShuffleWriter

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

3.8 Reader实现

3.8.1 Reader实现-网络时序图

image.png

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

3.8.2 Reader实现-ShuffleBlockFetchlterator

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

image (1).png

3.8.3 Read实现 - External Shuffle Service

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

image.png

3.9 Shuffle 优化使用的技术 -zero Copy

image.png

3.9.1 Shuffle 优化使用的技术:Netty Zerp Copy

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

3.10 常见问题

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

3.11 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预聚合的算子

image.png

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倾斜优化

  • 什么叫shuffle倾斜
  • 倾斜影响
    • 作业运行时间变长
    • Task OOM导致作业失败

image.png

  • 常见的倾斜处理方法
    • 提高并行度
      • 优点:足够简单
      • 缺点:只缓解,不根治

image.png

3.13.1 Spark AQE Skew Hoin

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

四、Push Shuffle

4.1为什么需要Push Shuffle

  1. Avg l0 size太小,造成了大量的随机I0,严重影响磁盘的吞吐
  2. M * R次读请求,造成大量的网络连接,影响稳定性 为了优化该问题,有很多公司都做了思路相近的优化,push shuffle

4.2 Magnet实现原理

image.png

  • 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

image.png

4.4 Magnet可靠性

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

4.5 Cloud Shuffle Service思想

image.png

4.6 Cloud Shuffle Service架构

image.png

  • Zookeeper WorkerList [服务发现]

  • CSS Worker [Partitions / Disk | Hdfs]

  • Spark Driver [集成启动 CSS Master]

  • CSS Master [Shuffle 规划 / 统计]

  • CSS ShuffleClient [Write / Read]

  • Spark Executor [Mapper + Reducer]

4.6.1 Cloud Shuffle Service 写入流程

image.png

4.6.2 Cloud Shuffle Service 读取流程

image.png

4.6.3 Cloud Shuffle Service AQE

一个Partition会最终对应到多个Epoch file,每个EPoch 目前设置是512M image.png