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

139 阅读4分钟

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

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


今天学习了课程《大数据Shuffle原理与实践》,学习的内容主要分为以下几个部分:

  • Shuffle概述
  • Shuffle算子
  • Shuffle过程
  • Push Shuffle

一、Shuffle概述

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

  • Map阶段,是在单机上进行的针对一小块数据的计算过程。
  • Shuffle阶段,在Map阶段的基础上,进行数据移动,为后续的reduce阶段做准备。
  • Reduce阶段,对移动后的数据进行处理,依然是在单机上处理一小份数据。

为什么Shuffle对性能非常重要呢?

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

二、Shuffle算子

2.1 Shuffle算子的分类

repartitionByKeyjoinDistinct
coalescegroupByKeycogroupdistinct
repartitionreduceByKeyjoin
aggragateByKeyleftOuterJoin
combineByKeyinsersection
sortByKeysubtract
sortBysubtractByKey

算子使用例子:

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

2.2 Spark中对Shuffle的抽象-宽依赖、窄依赖

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

算子内部的依赖关系:

  • ShuffleDependency

    • CoGroupedRDD

      • Cogroup

        • fullOuterJoin、rightOuterJoin、leftOuterJoin
        • join
    • ShuffledRDD

      • combineByKeyWithClassTag

        • combineByKey
        • reduceByKey
      • Coalesce

      • sortByKey

        • sortBy

Shuffle Dependency 构造

  • Partitioner

    • 两个接口

      • numberPartitions
      • getPartition
    • 经典实现

      • HashPartitioner
  • Aggregator

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

三、Shuffle过程

3.1 HashShuffle

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

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

  • 优点:不需要排序
  • 缺点:打开或创建的文件过多

3.2 SortShuffle

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

  • 优点:打开的文件少、支持map-side combine
  • 缺点:需要排序

3.3 Shuffle读数据

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

3.4 Shuffle过程的触发流程

image-20220805204233482.png

3.5 Register Shuffle

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

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

  • BypassMergeSortShuffleWriter:HashShuffle

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

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

    • 支持combine
    • 需要combine时,使用PartitionAppendOnlyMap,本质是个HashTable
    • 不需要combine时,PartitionPairBuffer本质是个Array

image-20220805204731679.png

3.7 ShuffleReader网络请求流程

ShuffleReader 使用netty作为网络框架提供服务,并接受reducetask的fetch请求。首先发起openBlocks请求获得streamId,然后再处理stream或者chunk请求。

  • ShuffleBlockFetchIterator

    • 区分loacl和remote节省网络消耗
    • 防止OOM
  • External Shuffle Service

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

3.8 Shuffle 优化

  • 避免shuffle --- 使用broadcast替代join

  • 使用map-side预聚合的算子

  • Shuffle参数优化

  • Shuffle倾斜优化:如:增大并发度,AQE

  • Netty 零拷贝

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

四、Push Shuffle

Shuffle过程中可能遇到的问题:

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

为了优化上述问题,我们使用push shuffle优化

为什么需要Push Shuffle?

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

Push Shuffle的实现

  • Facebook:cosco
  • LindedIn:magnet
  • Uber:Zeus
  • Alibaba:RSS
  • Tencent:FireStorm
  • Bytedance:Cloud Shuffle Service
  • Spark3.2:push based shuffle

五、个人总结与思考

通过本课程,了解了Spark中shuffle主要的实现机制以及spark shuffle的底层实现原理,了解了Shuffle的优化,包括一些调优的策略,受益匪浅。shuffle原理涉及MapReduce、Spark等计算引擎,通过对Shuffle原理的理解以及优化,可以使得使用Spark等计算引擎处理大规模数据时更加高效。

\