Shuffle | 青训营笔记

79 阅读3分钟

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

1. MapReduce的三个阶段

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

image.png

  • Shuffle阶段,在Map阶段的基础上,不同分区(不同分区可能在同一节点上,也可能不在同一节点上)之间进行数据移动,为后续的Reduce阶段做准备

image.png

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

image.png

2. 为什么Shuffle对性能非常重要

image.png

  • M * R 次网络连接
  • 大量的数据移动
  • 数据丢失风险
  • 可能存在大量的排序操作(Map阶段)
  • 大量的数据序列化、反序列化操作
    • 将Java对象或者内存中直观的数据转化成二进制流放入文件中,在从文件中读到内存里面来,重新变成一个对象
  • 数据压缩

3. Spark中会产生Shuffle的算子

repartitionByKeyjoindistict
coalescegroupByKeycogroupdistinct
repartitionreduceByKeyjoin
aggregateByKeyleftOuterJoin
combineByKeyintersection
sortByKeysubstract
sortBysubstractByKey

4. Spark中Shuffle的抽象 - 宽依赖、窄依赖

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

宽依赖:父RDD中的分片可能被子RDD中的多个分片所依赖

5. Hash Shuffle - 写数据

  • 每个partition会映射到一个独立的文件,每个Map Task都会给每个partition创建一个Buffer,写满了之后就flush到磁盘里,最终会生成 M * R 个文件

image.png

  • 出现的问题:

    • 生成的文件太多,对文件系统造成的压力太大
    • 而且在运行时,同时打开的文件也多,如果一个Map Task打开的文件太多,这个Map Task就有可能内存溢出
  • 写数据优化

    • 每个partition映射到一个文件片段

    • 同一核上运行的多个 Map Task 输出合并到同一个文件

      • 资源是有限的,比如一个stage有 10000 Map Task,因为资源有限,所以没有办法同时跑,只申请到了100个CPU来处理这些数据,可能只有100个Map Task同时跑,打开了100个文件,每个Map Task会往这个文件里写一个文件片段
      • 在执行下一批的Map Task时,会复用已经创建好的磁盘文件,即数据会继续写入到已有的磁盘文件
      • 该机制允许不同Map Task复用同一个磁盘文件,对于多个Map Task进行了一定程度的合并,减少了文件的数量,一定程度上提升了性能
    • 这样,最终会得到 C * R 个文件(C是申请到的CPU核数),但从M改成C,问题并没有根本性解决

image.png

6. Sort Shuffle - 写数据

  • 不再给每个partition一个Buffer了,给每个Task一个Buffer,这个Task中所有partition的数据都写到一个Buffer里面,当Buffer中的数据达到阈值的时候,通过排序的方式,把相同的partition数据放在一起,排序之后将数据分批溢写到磁盘(分批能够减少IO次数),所以叫Sort Shuffle。
  • 相比于Hash Shuffle,Sort Shuffle在排序上会消耗更多的CPU,好处是每个Task只会创建一个完整的文件(实际上是两个文件,还创建了一个indexfile去记录每一个FileSegment的偏移量),最终每个Task生成了一个包含所有partition的数据文件

image.png

7. Shuffle - 读数据

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

image.png