【笔记五】大数据shuffle原理与实践|青训营笔记

128 阅读6分钟

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

参考 【大数据专场 学习资料二】第四届字节跳动青训营

01.shuffle概述

  • Mapreduce

    • 2004谷歌论文:《MapReduce:Simplified Data Processing on Large Clusters》
    • 在开源实现的mapreduce中,存在map,shuffle,reduce三个阶段
  • 经典shuffle过程

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

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

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

  • 为什么shuffle如此重要

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

02.shuffle算子

  • 常见的触发shuffle的算子

    • repartition

      • coalesce、repartition
    • ByKey

      • groupByKey、reduceByKey、aggregateByKey、combineByKey、sortByKeysortBy
    • Join

      • cogroup、join、substract、leftOuterJoin
    • Distinct

      • distinct
  • 算子使用例子
val text = sc.textFile("mytextfile.txt")
val counts = text
  .flatMap(line => line.split(" "))
  .map(word => (word,1))
  .reduceByKey(_+_)
counts.collect
  • spark中对shuffle的抽象-宽依赖、窄依赖

    • 窄依赖:父RDD的每个分片至多被子RDD中的一个分片所依赖
    • 宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖
  • 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
    • 经典实现

      • HashPartition
  • Aggregator

    • 在map侧合并部分record的函数

    • 接口

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

03.shuffle过程

  • spark中的shuffle变迁过程

    • HashShuffle

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

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

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

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

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

    • BypassMergeSortShuffleWriter:HashShuffle
    • UnsafeShuffleWriter:TunstonShuffle
    • SortSHuffleWriter:SortShuffle
  • ShuffleReader网络请求流程

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

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

  • ShuffleBlockFetchIterator

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

    • 防止OOM

      • maxBytesInFlight
      • maxReqsInFlight
      • maxBlocksInFlightPerAddress
      • maxReqSizeShuffleToMem
      • maxAttemptsOnNettyOOM
  • External Shuffle Service

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

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

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

      • 什么叫倾斜?有什么危害

        以单词分区的案例为例:如果有极大量的数据并不是平衡分布的,在进行分区处理的时候可能绝大 多数的键值对最终会聚集于一个单一的Reducer之上,压倒这个Reducer,从而大大降低程序的性能,导致严重的数据倾斜。 Combiner机制虽然可以大大提升性能,避免某些场景下的数据倾斜,但并不是所有计算场景都适 合使用Combiner机制。 例如对海量数据计算平均值时,如果在每个MapTask阶段预先计算一个平均值,那么到Reducer 阶段对所有数据求和合再计算平均值时就会产生严重的误差。 因此Combiner机制在Shuffle过程中是可选的,我们需要根据业务来决定是否启用Combiner预 合并机制。

      影响:

       作业运行时间变长
      
      Task OOM导致作业失败
      
      • 解决倾斜方法举例

      提高并行度---Shuffle流程中的预合并机制(Combiner)就是为了避免map任务和reduce任务之间过多的数据传输从而发生数据倾斜而设置的

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

  • 零拷贝

    • sendfile+DMA gather copy
  • Netty 零拷贝

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

04.Push Shuffle

  • 上一部分所讲的shuffle过程存在哪些问题?

    • 数据存储在本地磁盘,没有备份
    • IO 并发:大量 RPC 请求(M*R)
    • IO 吞吐:随机读、写放大(3X)
    • GC 频繁,影响 NodeManager
  • 为了优化该问题,有很多公司都做了思路相近的优化,push shuffle

  • Magnet主要流程

主要为边写边push的模式,在原有的shuffle基础上尝试push聚合数据,但并不强制完成,读取时优先读取push聚合的结果,对于没有来得及完成聚合或者聚合失败的情况,则fallback到原模式。

  • Cloud Shuffle Service架构

    • Zookeeper WorkerList [服务发现]
    • CSS Worker [Partitions / Disk | Hdfs]
    • Spark Driver [集成启动 CSS Master]
    • CSS Master [Shuffle 规划 / 统计]
    • CSS ShuffleClient [Write / Read]
    • Spark Executor [Mapper + Reducer]
  • Cloud Shuffle Service 读写流程

  • Cloud Shuffle Service 支持AQE

    • —个Partition会最终对应到多个Epoch file,每个EPoch目前设置是512MB

    • 在聚合文件时主动将文件切分为若干块,当触发AQE时,按照已经切分好的文件块进行拆分。