这是我参与「第四届青训营 」笔记创作活动的第六天
Shuffle 一般被翻译为数据混洗,是类 MapReduce 分布式计算框架独有的机制,也是这类分布式计算框架最重要的执行机制。Shuffle主要分为两个层面:
逻辑层面
物理层面。
逻辑层面主要从 RDD 的血统机制出发,从 DAG 的角度来分析 Shuffle,Spark 容错机制,而物理层面是从执行角度来分析 Shuffle 是如何发生的。
RDD 血统与 Spark 容错
在 DAG 中,最初的 RDD 被称为基础 RDD,后续生成的 RDD 都是由算子以及依赖关系生成的,也就是说,无论哪个 RDD 出现问题,都可以由这种依赖关系重新计算而成。这种依赖关系被称为 RDD 血统(lineage)。血统的表现形式主要分为宽依赖(wide dependency)与窄依赖(narrow dependency)
窄依赖的定义是:子 RDD 中的分区与父 RDD 中的分区只存在一对一的映射关系,而宽依赖则是子 RDD 中的分区与父 RDD 中的分区存在一对多的映射关系,那么从这个角度来说,map、 filter、 union 等就是窄依赖,而 groupByKey、 coGroup 就是典型的宽依赖
宽依赖还有个名字,叫 Shuffle 依赖,也就是说宽依赖必然会发生 Shuffle 操作,在前面也提到过 Shuffle 也是划分 Stage 的依据。而窄依赖由于不需要发生 Shuffle,所有计算都是在分区所在节点完成,它类似于 MapReduce 中的 ChainMapper。所以说,在你自己的 DAG 中,如果你选取的算子形成了宽依赖,那么就一定会触发 Shuffle。
当 RDD 中的某个分区出现故障,那么只需要按照这种依赖关系重新计算即可,窄依赖最简单,只涉及某个节点内的计算,而宽依赖,则会按照依赖关系由父分区计算而得到。
spark 的容错主要分为资源管理平台的容错和 Spark 应用的容错, Spark 应用是基于资源管理平台运行,所以资源管理平台的容错也是 Spark 容错的一部分,如 YARN 的 ResourceManager HA 机制。在 Spark 应用执行的过程中,可能会遇到以下几种失败的情况:
Driver 报错;
Executor 报错;
Task 执行失败。
Driver 执行失败是 Spark 应用最严重的一种情况,标志整个作业彻底执行失败,需要开发人员手动重启 Driver;Executor 报错通常是因为 Executor 所在的机器故障导致,这时 Driver 会将执行失败的 Task 调度到另一个 Executor 继续执行,重新执行的 Task 会根据 RDD 的依赖关系继续计算,并将报错的 Executor 从可用 Executor 的列表中去掉;Spark 会对执行失败的 Task 进行重试,重试 3 次后若仍然失败会导致整个作业失败。在这个过程中,Task 的数据恢复和重新执行都用到了 RDD 的血统机制。
Spark Shuffle
很多算子都会引起 RDD 中的数据进行重分区,新的分区被创建,旧的分区被合并或者被打碎,在重分区的过程中,如果数据发生了跨节点移动,就被称为 Shuffle,在 Spark 中, Shuffle 负责将 Map 端(这里的 Map 端可以理解为宽依赖的左侧)的处理的中间结果传输到 Reduce 端供 Reduce 端聚合(这里的 Reduce 端可以理解为宽依赖的右侧),它是 MapReduce 类型计算框架中最重要的概念,同时也是很消耗性能的步骤。Shuffle 体现了从函数式编程接口到分布式计算框架的实现。与 MapReduce 的 Sort-based Shuffle 不同,Spark 对 Shuffle 的实现方式有两种:Hash Shuffle 与 Sort-based Shuffle,这其实是一个优化的过程。在较老的版本中,Spark Shuffle 的方式可以通过 spark.shuffle.manager 配置项进行配置,而在最新的 Spark 版本中,已经去掉了该配置,统一称为 Sort-based Shuffle。