「闻数起舞」为什么Apache Spark速度很快以及如何使其运行更快( 二 )



「闻数起舞」为什么Apache Spark速度很快以及如何使其运行更快
本文插图

> A wide transformation (Source: Databricks)
因此 , 当您将作业提交给Spark时 , 您提交的基本上是一系列操作和转换 , 然后由Catalyst转换为作业的逻辑计划 , 然后生成理想的物理计划 。 第二部分:Spark 魔术
现在 , 我们知道了Spark如何看待提交给它的工作 , 让我们研究一下将动作和转换列表转换为工作的物理执行计划的机制 。 Spark是个懒惰的魔术师
首先 , 使用Spark时要记住的一个重要概念是它依赖于惰性评估 。这意味着 , 当您提交作业时 , Spark只会在必须执行时(即 , 当它收到一个动作时(例如 , 当驱动程序要求一些数据或何时需要将数据存储到HDFS中))发挥其魔力 。
Spark无需立即一一运行转换 , 而是将这些转换存储在DAG(有向无环图)中 , 并且一旦接收到动作 , 它就会运行整个DAG并交付请求的输出 。这样一来 , 它就可以基于作业的DAG优化其执行计划 , 而无需顺序运行转换 。 一切如何发生
Spark依靠其优化器Catalyst进行必要的优化 , 以生成最有效的执行计划 。Catalyst的核心包括一个通用库 , 专用于表示树并应用规则来操纵它们 。它利用Scala中的函数式编程构造 , 并提供特定于关系查询处理的库 。
Catalyst的主要数据类型是由节点对象组成的树 , 该树上应用了一组规则对其进行优化 。这些优化通过四个不同的阶段执行 , 如下图所示:

「闻数起舞」为什么Apache Spark速度很快以及如何使其运行更快
本文插图

> Catalyst's optimization phases (source: Databricks)
逻辑/物理计划
一开始可能不是很清楚的区别是术语"逻辑计划"和"物理计划"的使用 。简而言之 , 逻辑计划由一棵树组成 , 该树描述了需要做的事情 , 而没有暗示如何做 , 而物理计划则恰好描述了树中每个节点将要做什么 。
例如 , 逻辑计划仅表示需要执行联接操作 , 而物理计划则为该特定操作修复了联接类型(例如ShuffleHashJoin) 。
现在 , 我们来完成这四个步骤 , 并深入研究Catalyst的逻辑 。 步骤1:分析
Catalyst优化管道的起点是一组未解决的属性引用或关系 。无论您使用的是SQL还是DataFrame / Dataset API , SparkSQL最初都不会对您的数据类型或您所指的列是否存在(这就是未解决的意思)一无所知 。如果您提交选择查询 , SparkSQL将首先使用Catalyst来确定您传递的每一列的类型以及您所使用的列是否实际存在 。为此 , 它主要依赖于Catalyst的树和规则机制 。
它首先为未解决的逻辑计划创建一棵树 , 然后开始在其上应用规则 , 直到解析所有属性引用和关系 。在整个过程中 , Catalyst依赖于Catalog对象 , 该对象跟踪所有数据源中的表 。 步骤2:逻辑优化
在此阶段 , Catalyst获得了一些帮助 。随着2017年Spark 2.2的发布 , 引入了基于成本的优化器框架 。与基于规则的优化相反 , 基于成本的优化器使用统计信息和基数来查找最有效的执行计划 , 而不是简单地应用一组规则 。
分析步骤的输出是一个逻辑计划 , 然后在第二步中进行一系列基于规则和基于成本的优化 。Catalyst将所有优化规则应用于逻辑计划 , 并与基于成本的优化器一起使用 , 以将优化的逻辑计划交付至下一步 。 步骤3:物理规划
就像上一步一样 , SparkSQL将Catalyst和基于成本的优化器同时用于物理规划 。在利用一组物理规则和统计信息提供最有效的物理计划之前 , 它会基于优化的逻辑计划生成多个物理计划 。 步骤4:代码生成
最后 , Catalyst使用准符号(Scala提供的特殊功能)来生成要在每台计算机上运行的Java字节码 。Catalyst通过将作业的树转换为由Scala评估的抽象语法树(AST)来使用此功能 , 然后由该树编译并运行生成的代码 。 总结一下


推荐阅读