shuffle | 青训营笔记

124 阅读5分钟

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

1.概述

在04年谷歌发布的《MapReduce:Simplified Data Processing on Large Clusters》论文中,提到了shuffle的概念。

1.1 一个经典的MapReduce过程

  • Map:在单机上,对一小份数据并行进行处理
  • Shuffle:在map的基础上,将数据进行分区、排序、合并后形成有一定规律的数据,为后续的reduce阶段做准备
  • Reduce:汇总整理的阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据

1.2 shuffle的对性能的影响

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

1.3 总结

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

2. shuffle算子

常见的触发shuffle的算子 image.png 算子使用例子

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

Shuffle Dependency

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

创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle相关的信息。构造函数中有以下步骤:

  • Partitioner——用来将record映射到具体的partition的方法

    • numberPartitions
    • getPartition
  • Aggregator——在map侧合并部分record的函数

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

3.shuffle过程

3.1 spark中的shuffle变迁过程

  • HashShuffle 0.8之前,2.0退出

    • 优点:不需要排序
    • 缺点:因为每个partition会映射到一个文件,同时打开,创建的文件过多
    • 优化:写数据优化,使得每个partition都映射到一个文件片段
  • SortShuffle 1.1引入,1.2默认

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

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

3.2 shuffle注册

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

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

    • 不支持mapside combine && partitions小于 spark.shuffle.sort.bypassmerge Threshold——BypassMergeSortShuffleHandle

      • 不支持mapside combine && serialize支持relocation&& partition小于 224——SerializedShuffleHandle
      • !(不支持mapside combine && serialize支持relocation&& partition小于 224)——BaseShuffleHandle

3.3 Writer实现

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

    • BypassMergeSortShuffleWriter:HashShuffle

      • 不需要排序,节省时间,写操作的时候会打开大量的文件
    • UnsafeShuffleWriter:TunstonShuffle

      • 利用类似内存页存储序列化数据,数据写入后不再反序列化
      • 只根据partition排序Long Array,数据不移动
    • SortSHuffleWriter:SortShuffle

      • 支持combine,需要combine时,使用PartitionedAppendOnlyMap,本质是个HashTable;不需要combine的时候PartitionedPairBuffer本质是个Array

3.4 Reader实现

  • ShuffleReader网络请求流程

    • img

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

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

位置信息记录在MapOutputTracker中

Reader实现:ShuffleBlockFetchIterator

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

  • 防止OOM

    • maxBytesInFlight:限制获取数据块的大小
    • maxReqsInFlight:限制获取数据的数量
    • maxBlocksInFlightPerAddresst:限制每一个地址上获取block的数量
    • maxReqSizeShuffleToMemt:限制内存中的请求大小
    • maxAttemptsOnNettyOOM:限制OOM的次数

Reader实现:External Shuffle Service——解耦数据计算和服务

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

3.5 shuffle优化使用的技术

3.5.1 零拷贝

DMA:直接存储器存取,指外部设备不通过cpu而直接与系统内存交换数据的接口技术

  • 零拷贝——Zero Copy

    • sendfile+DMA gather copy
  • Netty 零拷贝

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

3.5.2避免shuffle

  1. 使用broadcast替代join,传统的join操作会导致shuffle操作。
  2. 使用可以map-side预聚合的算子

3.5.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

3.5.4 Shuffle 倾斜优化

  • 什么叫倾斜?数据处理量不平均
  • 有什么危害?影响处理效率,作业运行时间变长;Task OOM导致作业失败
  • 解决倾斜方法举例
    • 增大并发度【足够简单,但是只缓解,不根治】
    • AQE:根据shuffle文件统计数据自动检测倾斜数据,将那些倾斜分区打散成小的子分区,然后各自进行join

4. Push Shuffle

  • shuffle过程存在哪些问题?

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

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

    参考

    ppt笔记