大数据-复习笔记-6 | 青训营笔记

217 阅读6分钟

大数据 Shuffle 原理与实践 - 复习笔记

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

1. shufle概述

1.1 MapReduce 概述

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

image.png

1.2 Map阶段

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

1.3 Shuffle阶段

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

1.4 Reduce 过程

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

1.5 为什么 shuffle 对性能非常重要

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

1659425227185.png

1.6 总结

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

2. shufle算子

2.1 Shuffle 算子分类

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

repartitionByKeyjoinDistinct
coalescegroupByKeycogroupdistinct
repartitionreduceByKeyjoin
aggregateByKeyleftOuterJoin
combineByKeyintersection
sortByKeysubtract
sortBysubtractByKey

2.2 Shuffle 算子应用

  • Spark源码中RDD的单元测试
  • Spark源码中PairRDDFunctions的单元测试
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中的多个分片所依赖

1659425793473.png

2.4 算子内部的依赖关系

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

2.4.1 Shuffle Dependency 构造

  • A single key-value pair RDD,i.e.RDDIProduct2[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.

2.4.2 Shuffle Dependency 构造-Partitioner

  • 两个接口
    • numberPartitions
    • getPartition
  • 经典实现
    • HashPartitioner
abstract class Partitioner extends Serializable {
    def numPartitions:Int
    def getPartition(key:Any):Int
}
class HashPartitioner(partitions: Int) extends Partitioner{
    require(partitions >= 0, s"Number of partitions ($partitions) cannot be negative";
    
    def numpartitions: Int = partitions
    
    def getPartition(key: Any): Int = key match{ 
    case null => 0 
    case _ => Utils. nonNegativeMod(key.hashCode, numPartitions)
}

2.4.3 Shuffle Dependency构造-Aggregator

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

3. shufle过程

3.1 Hash Shuffle-写数据

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

1659427844852.png

3.1.1 Hash Shuffle-写数据优化
每个partition会映射到一个文件片段

1659428057281.png

3.2 Sort shuffle:写数据

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

1659428171537.png

3.3 Shuffle-读数据

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

1659428242168.png

3.4 Shuffle 过程的触发流程

1659428557765.png

val text = sc.textFile("mytextfile.txt")
val counts = text
    .flatNap(line => 1ine, split(" "))
    .map(word => (word,l))
    .reducelyKey(_+_)
counts.collect

3.5 Shuffle Handle的创建

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

1659428658215.png

3.6 Shuffle Handle 与 Shuffle Writer的对应关系

1659428708111.png

3.7 Writer 实现 - BypassMergeShuffleWriter

3.7.1 Writer 实现 - BypassMergeShuffleWriter

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

1659428796839.png

3.7.2 Writer 实现 - UnsafeShuffleWriter

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

1659428942511.png

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

1659428989017.png

3.7.3 Writer 实现 - SortShuffleWriter

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

1659429074491.png

3.8 Reader 实现-网络时序图

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

image.png

3.8.1 Reader 实现-ShuffleBlockFetchlterator

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

1659431360946.png

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

1659431395266.png

3.9 Shuffle优化使用的技术-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
    • 使用broadcast替代join
  • 使用可以map-side预聚合的算子

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.targetPostShufflelnputSize
  • spark.reducer.maxSizelnFlight·spark.reducer.maxReqsinFlight
  • spark.reducer.maxBlocksInFlightPerAddress

3.13 Shuffle 倾斜优化

  • 什么叫shuffle倾斜
  • 倾斜影响
    • 作业运行时间变长
    • Task OOM导致作业失败
  • 常见的倾斜处理办法
    • 提高并行度
      • 优点:足够简单
      • 缺点:只缓解、不根治
  • Spark AQE Skew Join
    AQE 根据 shuffle文件统计数据自动检测倾斜数据,将那些倾斜的分区打散成小的子分区,然后各自进行join。

1659431756017.png

3.14 案例-参数优化

  • ad show
    • number of files read:840,042
    • number of total tasks:5,553
    • size of files read:203.3 TiB
    • number of output rows:128,676,054,598
select 
    ad_type, 
    sum(click), 
    sum(duration)
from ad_show 
where date = '20220101'
group by
    ad_type

3.15参数调整

  • spark.sql.adaptive.shuffle.targetPostShufflelnputSize:64M->512M
  • spark.sql.files.maxPartitionBytes:1G->40G

4. Push Shuffle

4.1 为什么需要Push Shuffle?

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

4.2 Push Shuffle 的实现

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

4.3 Magnet 实现原理

  • 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

4.4 Magnet 可靠性

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

4.5 Cloud Shuffle Service 架构

  • Zookeeper WorkerList[服务发现]
  • CSS Worker[Partitions/Disk IHdfs]
  • Spark Driver[集成启动CSS Master]
  • CSS Master[Shuffle 规划/统计]
  • CSS ShuffleClient[Write/Read]
  • Spark Executor[Mapper+Reducer]

image.png


5. 课程总结

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