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

94 阅读4分钟

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

知识点小记

shuffle概述

1.png

       第一步的分布式处理过程叫做Map,分成单份数据处理叫做Reduce,在中间移动数据的过程叫做shuffle。

Shuffle算子

       算子会产生shuffle是因为它需要做数据移动,当我们需要计算的内容需要移动的时候就会产生shuffle。

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

  1. repartition:重新改变分区。
  2. ByKey:给定一个KV对把key聚合到一起计算的算子。
  3. join:把本身没有在一起的数据按照某种条件放到一起的算子。
  4. Distinct:一种特殊的ByKey操作。

Shuffle Dependency

       创建会产生shuffle的RDD时,RDD会创建Shuffle Dependency来描述Shuffle的相关信息。Shuffle Dependency是Spark中对shuffle的抽象。这个对象构造函数中包含了若干的变量:

  1. 一个KV对形式的RDD
  2. Partitioner:负责给定一个K产生一个这个K所对应的分区
  3. Serializer:负责把对象映射成一段二进制的数据流或把一个二进制的数据映射成一个对象
  4. K是否需要排序的flag
  5. Aggragator
  6. 描述shuffle过程是否有mapSideCombine的flag

Shuffle Dependency构造中最重要的两个对象:

  1. Partitioner:负责把一个k映射成一个数字,这个数字就代表了某一个具体的分区。Partitioner是一个抽象类,它有两个接口:
  • numberPartitions:一共有多少个分区
  • getPartition:给定一个K,给出该K对应的分区是什么
  1. Aggregator:进行shuffle时一个非常重要的性能优化器,它有三个方法:
  • createCombiner:只有一个value的时候初始化的方法
  • mergeValue:初始化好一个Aggregator之后,如果再来一个value,则调用此方法把该value合并到Aggregator中。
  • mergeCombiners:合并两个Aggregator

shuffle过程

写数据:

Hash Shuffle:spark中最早的shuffle的实现,把不同的partition写到不同的文件中,每个map task都会给每个partition创建一个buffer,写满了之后就会flash到磁盘中,这样的话最终会生成mrm*r个文件。随着作业规模的增大,会对文件系统造成的压力较大。优先是不排序。

Sort Shuffle:所有的数据都写到一个buffer里面,当内存满的时候,通过排序的方式,把相同的partition放在一起。优点是打开的文件少,支持map-side combine,缺点是需要排序。

读数据:每一个reduce task分别获取所有map task生成的属于自己的片段。

Shuffle Handle的创建

2.png        RegisterShuffle中做的最重要的事情就是创建Shuffle Handle,根据shuffle不同的条件生成不同的Shuffle Handle。

三种ShuffleHandle与Shuffle Writer的对应关系

BypassMergeSortShuffleHandle -> BypassMergeSortShuffleWriter:HashShuffle

SeriallzedShuffeHandle -> UnsafeShuffleWriter:TunstonShuffle

BaseShuffleHandle -> SortSHuffleWriter:SortShuffle

Shuffle Writer的实现方式

BypassMergeSortShuffleWriter:类似于Hash Shuffle,不排序,节省cpu,不同的partition灌入不同的文件。缺点是只适用于partition较少的情况。

UnsafeShuffleWriter:在历史中最开始叫TunstonShuffle。使用类似内存页存储序列化数据,数据写入后不再反序列化。只根据partition进行排序。

SortSHuffleWriter:支持combine,首先会对partition做排序,然后最KV 里的K进行排序。内存慢了之后触发spill,将排序后的数据放到外部存储中。

Shuffle Reader的实现方式

3.png

       使用netty作为网络框架提供网络服务,接受reducetask的fetch请求,从MapOutputTracker中获取数据所在的位置,根据数据所在的位置分为本地和远程,对于远程的数据需要构建请求。主要通过OpenBlocks请求、Chunk请求或Stream请求。具体实现利用ShuffleBlockFetchIterator实现。

shuffle优化

  1. 避免shuffle:用broadcast代替join。
  2. 使用可以map-side预聚合的算子:如在单词计数时提前算一下hello的数可减少shuffle的数据量。
  3. 对shuffle参数优化,根据作业的不同表现来调整对应的shuffle参数。
  4. shuffle倾斜优化。可能经过shuffle后某一个reduce分到的数据非常非常的多而其他的reduce分到的task分到的非常少,此时使总体的作业变慢,也可能会因为资源不够而导致作业失败。

Push Shuffle

       Avg IO size太小,造成了大量的随机IO,严重影响磁盘的吞吐。且mrm*r次请求,造成大量的网络连接,影响稳定性。为了解决这些问题,业界有一个共同的思路,能不能在读取shuffle数据之前,就把这些数据合并到一起,这样就可以避免随机读的问题了。针对合并的方法,Push Shuffle应运而生。