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

111 阅读4分钟

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

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

Shuffle算子

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

image.png

Spark中对shuffle的抽象

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

算子内部的依赖关系

Shuffle Dependency

  1. 构造:
    1. A single key-value pair RDD

    2. partitioner:

      1. numberPartitions接口
      2. getPartition接口
    3. serializer

    4. keyOrdering

    5. aggregator

      1. createCombiner:只有一个value的时候初始化的方法
      2. mergeValue:合并一个value到Aggregator
      3. mergeCombiners:合并两个Aggregator
    6. mapSideCombine

Shuffle过程

触发流程

  1. Collect Action:
  2. SubmitJob:
  3. GetDependencies:
  4. RegisterShuffle:

Shuffle Handle的创建

Register Shuffle做的最重要的事情是根据不同条件创建不同的Shuffle Handle

image.png

Shuffle Handle与Shuffle Writer的对用关系

image.png

BypassMergeSortShuffleWriter实现细节

BypassMergeSortShuffleWriter和Hash Shuffle中的HashShuffleWriter实现基本一致, 唯一的区别在于,map端的多个输出文件会被汇总为一个文件。 所有分区的数据会合并为同一个文件,会生成一个索引文件,是为了索引到每个分区的起始地址,可以随机 access 某个partition的所有数据。

image.png

但是需要注意的是,这种方式不宜有太多分区,因为过程中会并发打开所有分区对应的临时文件,会对文件系统造成很大的压力。

具体实现就是给每个分区分配一个临时文件,对每个 record的key 使用分区器(模式是hash,如果用户自定义就使用自定义的分区器)找到对应分区的输出文件句柄,直接写入文件,没有在内存中使用 buffer。 最后copyStream方法把所有的临时分区文件拷贝到最终的输出文件中,并且记录每个分区的文件起始写入位置,把这些位置数据写入索引文件中。

UnsafeShuffleWriter实现细节

image.png

  1. 将record进行分区并序列化后插入sorter。
  2. 将record进行排序,并在排序完成后写入磁盘文件作为spill file,再将多个spill file合并成一个输出文件。
@Override
  public void write(scala.collection.Iterator<Product2<K, V>> records) throws IOException {
    // Keep track of success so we know if we encountered an exception
    // We do this rather than a standard try/catch/re-throw to handle
    // generic throwables.
    boolean success = false;
    try {
      //将record进行分区并序列化后插入sorter
      while (records.hasNext()) {
        insertRecordIntoSorter(records.next());
      }
      //将record进行排序,并在排序完成后写入磁盘文件作为spill file,再将多个spill file合并成一个输出文件。
      closeAndWriteOutput();
      success = true;
    } finally {
      if (sorter != null) {
        try {
          sorter.cleanupResources();
        } catch (Exception e) {
          // Only throw this error if we won't be masking another
          // error.
          if (success) {
            throw e;
          } else {
            logger.error("In addition to a failure during writing, we failed during " +
                         "cleanup.", e);
          }
        }
      }
    }
  }

SortShuffleWriter实现细节

image.png

SortShuffleWriter实现过程

  1. 首先无法对map输出进行写出优化时(Serialized或者BypassMergeSort),匹配到SortShuffleWriter。
  2. 调用SortShuffleWriter的write方法开始写出准备。
  3. 创建ExternalSorter对map输出进行排序及合并写出到磁盘。
  4. 写出索引文件(记录分区在对应磁盘文件中的Segment偏移)。

SortShuffleWriter在写入时,会根据是否有mapSideCombine选择使用不同的数据结构来进行排序。有mapSideCombine,那么采取map或buffer。

归并排序分为两个阶段,第一阶段是分片输出有序文件,第二阶段是归并输出整体有序文件。

Reader实现

网络时序图

image.png

使用基于netty的网络通信框架

位置信息记录在MapOutputTrack中

主要会发送两种类型的请求:openBlocks请求、Chunk请求或Stream请求

ShuffleBlockFetchIterator

image.png

区分local和remote节省网络消耗

详解见[:]([SPARK][CORE] 面试问题之 Shuffle reader 的细枝末节 (下) - 知乎 (zhihu.com))

External Shuffle Service

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

下图展示了一个 shuffle 操作,有两台主机分别运行了三个 Map 节点。Map 节点生成完 shuffle 数据后,会将数据的文件路径告诉给 ExternalShuffleService。之后的 Reduce 节点读取数据,就只和 ExternalShuffleService 交互。

image.png

Shuffle 优化使用的技术

Zero Copy

零拷贝指的是,从一个存储区域到另一个存储区域的copy任务没有CPU参与。零拷贝通常用于网络文件传输,以减少CPU消耗和内存带宽占用,减少用户空间(用户可以操作的内存缓存区域)与CPU内核空间(CPU可以操作的内存缓存区域及寄存器的拷贝过程,减少用户上下文(用户状态环境)与CPU内核上下文(CPU内核状态环境)间的切换,提高系统效率

image.png

Shuffle中合并文件、发送数据会用到zero copy