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

126 阅读7分钟

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

这是我参与「第四届青训营 -大数据场」笔记创作活动的第8天

一、Shuffle概述

1. MapReduce概述

  • MapReduce存在Map、Shuffle、Reduce三个阶段

    • Map阶段:在单机上对一小块数据进行计算的过程
    • Shuffle阶段:在map处理好的数据上进行数据移动,为reduce作准备
    • Reduce阶段:对移动后的数据进行处理,在单机上处理一小块数据

2. Shuffle对性能重要

  • M*R次网络连接 (网络请求、server压力大)
  • 大量的数据移动
  • 数据丢失风险
  • 存在大量的排序操作
  • 大量的数据序列化、反序列化操作 (转换为2进制数据流存储、读取,消耗CPU)
  • 数据压缩和解压缩

image.png

3.总结

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

二、Shuffle算子

1. Shuffle算子分类

RepartitionByKeyJoinDistinct
coalescegroupByKeycogroupdistinct
repartitionreduceByKeyjoin
aggregateByKeyleftOuterJoin
combineByKeyintersection
sortByKeysubtract
sortBysubtractByKey

2. Shuffle算子应用

3. Spark中对Shuffle的抽象

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

4. 算子内部的依赖关系

4.1 shuffleDependency
  • CoGroupedRDD

    • CoGroup

      • fullOuterJoin, left/rightOuterjoin
      • join
  • ShuffledRDD

    • combineByKeyWithClassTag

      • combineByKey
      • reduceByKey
    • coalesce

    • sortByKey

      • sortBy
4.2 构造
  • 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过程

1. Hash Shuffle

  • 写数据:每个partition会映射到一个独立的文件,写满后flush到内存。
  • 问题:生成的文件、同时打开的文件太多 (M*R),消耗占用的资源过多。

image.png

  • 写数据优化:把每个partition映射成一个文件的片段 只需要申请可以申请到的文件(C*R)
  • 问题:仍然需要打开过多文件 (R个)

image.png

2. Sort Shuffle

  • 写数据:每个task生成一个包含所有partition数据的文件。

image.png

3. Shuffle-读数据

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

image.png

4. Shuffle过程的触发流程

image.png

5. Shuffle Handle的创建

6. Shuffle Handle 与Shuffle Writer的对应关系

7. Writer实现

7.1 BypassMergeShuffleWriter
  • partition较少 200
  • 不需要排序,节省时间
  • 写操作的时候会打开大量文件
  • 类似于hash shuffle,但会将partition file merge到一起

image.png

7.2 UnsafeShuffleWriter
  • 不能超过2^24 (前24bit记录partition id,不能溢出)

  • 对外内存:unsafe

    • 没有垃圾回收开销
    • 只按照partition进行排序,不排序record
    • record放到内存页 写满后申请新的内存页
    • 写满后触发spill,merge spill file生成两个文件
  • 类似内存页储存序列化数据,写入后不再反序列化

  • 只根据partition排序long array (记录record对应partition id、page num和data offset 不移动数据

image.png

image.png

7.3 SortShuffleWriter
  • 支持combine

    • 需要combine时使用partitionedAppendOnlyMap,本质是个HashTable
    • 不需要combine时PartitionedPairBuffer本质是个array
  • 需要对内内存,因为需要比较key所以如果放在对外内存需要反序列化

image.png

8. Reader的实现

8.1 网络时序图
  • 使用基于netty的网络通信框架

    • 申请对外内存
  • 位置信息记录在mapOutputTracker中

    • 存放在本地和远程,若在远程需要发送请求
  • 主要会发送两种类型请求

    • 客户端发送openBlocks请求
    • 客户端接收到响应后:发送Chunk请求或Stream请求
  • 客户端收取数据后,存储并解析数据

image.png

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

  • 对远端构造fetch request对象,添加到队列中(顺序随机,防止热点)

  • fetchUpToMaxBytes:从队列中取出对象,发送request请求

  • fetchLocalBlocks:获取本地blocks

  • 防止OOM内存溢出

    • maxBytesInFlight:限制数据块大小
    • maxReqsInFlight:限制请求数量
    • maxBlocksInFlightPerAddress:限制每个地址上block获取数量
    • maxReqSizeShuffletoMem:最大进入内存的请求size
    • maxAttemptsOnNettyOOM:控制对外OOM次数

image.png

8.3 External Shuffle Service
  • 解耦数据计算和数据shuffle。
  • ESS作为一个存在于每个节点上的agent为所有shuffle reader提供服务,从而优化Spark作业的资源利用率。
  • MapTask在运行结束后可以正常退出。
  • Map汇报shuffle数据存储的文件位置到ESS,Reduce端读取ESS输出的Shuffle数据。

image.png

9. Shuffle优化使用的技术

Netty Zero Copy

  • 可堆外内存,避免JVM堆内到堆外内存数据拷贝
  • compositeByteBuf、Unpooled.wrappedBuffer、bytebuf.slice可以合并、包装、切分数组,避免发生内存拷贝 (合并数组可以直接包装整体,对外提供整体的内容)
  • Netty使用FileRegion实现文件传输,FileRegion底层封装了FileChannel#transferTo方法,可以将文件缓冲区数据直接传送到目标channel,避免内核和用户侧缓冲区进行的拷贝

10. 常见问题

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

11. Shuffle优化

  • 避免shuffle使用broadcast替代join
  • 使用可以map-side预聚合的算子

12. Shuffle常见优化

  • shuffle参数优化

  • shuffle倾斜优化

    • 有的task非常热点

    • 容易导致作业运行时间变长、TaskOOM作业失败

    • 解决方法

      • 提高并行度:足够简单/只缓解、不根治
      • AQE skew join:根据shuffle文件统计数据自动检测倾斜,将倾斜分区打散成为小的子分区,然后各自进行join

四、Push Shuffle

1. 为什么需要Push Shuffle

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

2. Push Shuffle的实现

image.png

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的位置
  • currentMapId:标识当前正在append的block,保证不同的mapper的block能依次append

4. Magnet可靠性

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

5. Cloud Shuffle Service

5.1 思想

image.png

5.2 架构
  • Zookeeper WorkerList:服务发现
  • CSS worker:向zookeeper注册,partitions/Disk|HDFS
  • Spark Driver:集成启动CSS Master
  • CSS Master:shuffle规划/统计
  • CSS ShuffleClient:读写
  • SparkExecutor:Mapper/Reducer

image.png

5.3 写入流程

image.png

5.4 读取流程

image.png

5.5 AQE
  • 大文件会被读很多遍,跳过很多行
  • 切分大文件变为一般大的文件 Epoch files 512MB

课程总结

1. Shuffle概述

  • 什么是shuffle,shuffle的基本流程
  • 为什么shuffle对性能影响非常重要

2. Shuffle算子

  • 常见的shuffle算子
  • 理解宽依赖和窄依赖,ShuffleDependency及其相关组件

3. Shuffle过程

  • Spark中shuffle实现的历史
  • Spark中主流版本的shuffle写入和读取过程

4.Push shuffle

  • Magnet Push Shuffle的设计思路
  • Cloud Shuffle Service 的设计实现思路