从Spark SQL 底层架构可以看到,我们写的SQL语句,经过一个优化器(Catalyst)处理,转化为可执行的RDD,提交给集群执行。
SQL到RDD中间经过了一个Catalyst,它便是Spark SQL的核心,是针对Spark SQL语句执行过程中的查询优化框架,基于Scala函数式编程结构。
1、SparkSql执行架构

Catalyst的工作流程是一条SQL语句生成执行引擎可识别的程序,就离不开解析(Parser)、优化(Optimizer)、执行(Execution) 这三大过程。而Catalyst优化器在执行计划生成和优化的工作时候,它离不开自己内部的五大组件,如下所示:
(1)Parser模块:将SparkSql字符串解析为一个抽象语法树/AST。
(2)Analyzer模块:该模块会遍历整个AST,并对AST上的每个节点进行数据类型的绑定以及函数绑定,然后根据元数据信息Catalog对数据表中的字段进行解析。
(3)Optimizer模块:该模块是Catalyst的核心,主要分为RBO和CBO两种优化策略,其中RBO是基于规则优化,CBO是基于代价优化。
(4)Planner模块:优化后的逻辑执行计划OptimizedLogicalPlan依然是逻辑的,并不能被Spark系统理解,此时需要将OptimizedLogicalPlan转换成physical plan(物理计划) 。
(5)CostModel模块:主要根据过去的性能统计数据,选择最佳的物理执行计划,这个过程的优化就是CBO(基于代价优化)。

2、SQL执行过程
为了更好的对整个过程进行理解,下面通过简单的实例进行解释。
2.1、 Parser阶段:未解析的逻辑计划

Parser简单说就是将SQL字符串切分成一个一个的Token,再根据一定语义规则解析成一颗语法树。Parser模块目前都是使用第三方类库ANTLR进行实现的,包括我们熟悉的Hive、Presto、SparkSQL等都是由ANTLR实现的。

在这个过程中,会判断SQL语句是否符合规范,比如select from where 等这些关键字是否写对。
2.2、Analyzer阶段:解析后的逻辑计划
通过解析后的逻辑计划基本有了骨架,此时需要基本的元数据信息来表达这些词素,最重要的元数据信息主要包括两部分:表的Scheme和基本函数信息,表的Scheme主要包括表的基本定义(列名、数据类型)、表的数据格式(Json、Text)、表的物理位置等,基本函数主要指类信息。
Analyzer会再次遍历整个语法树,对树上的每个节点进行数据类型绑定及函数绑定,比如people词素会根据元数据表信息解析为包含age、id以及name三列的表,people.age会被解析为数据类型的int的变量,sum被解析为特定的聚合函数。

2.3、Optimizer模块:优化过的逻辑计划
Optimizer优化模块是整个Catalyst的核心,上面提到优化器分为基于规则的优化(RBO)和基于代价优化(CBO)两种。基于规则的优化策略实际上就是对语法树进行一次遍历,模式匹配能够满足特定规则的节点,在进行相应的等价转换。下面介绍三种常见的规则:谓词下推(Predicate Pushdown) 、常量累加(Constant Folding) 、列值裁剪(Column Pruning) 。
-
谓词下推(Predicate Pushdown)

上图左边是经过解析后的语法树,语法树中两个表先做join,之后在使用age>10进行filter。join算子是一个非常耗时的算子,耗时多少一般取决于参与join的两个表的大小,如果能够减少参与join两表的大小,就可以大大降低join算子所需的时间。
谓词下推就是将过滤操作下推到join之前进行,之后再进行join的时候,数据量将会得到显著的减少,join耗时必然降低。 -
常量累加(Constant Folding)

常量累加就是比如计算x+(100+80)->x+180,虽然是一个很小的改动,但是意义巨大。如果没有进行优化的话,每一条结果都需要执行一次100+80的操作,然后再与结果相加。优化后就不需要再次执行100+80操作。
列值裁剪(Column Pruning)
列值裁剪是当用到一个表时,不需要扫描它的所有列值,而是扫描只需要的id,不需要的裁剪掉。这一优化一方面大幅度减少了网络、内存数据量消耗,另一方面对于列式存储数据库来说大大提高了扫描效率。
2.4、SparkPlanner模块:转化为物理执行计划
根据上面的步骤,逻辑执行计划已经得到了比较完善的优化,然而,逻辑执行计划依然没办法真正执行,他们只是逻辑上可行,实际上Spark并不知道如何去执行这个东西。比如join是一个抽象概念,代表两个表根据相同的id进行合并,然而具体怎么实现合并,逻辑执行计划并没有说明。
此时就需要将逻辑执行计划转化为物理执行计划,也就是将逻辑上可行的执行计划变为Spark可以真正执行的计划。比如join算子,Spark根据不同场景为该算子制定了不同的算法策略,有BroadcastHashJoin、ShuffleHashJoin以及SortMergejoin等,物理执行计划实际上就是在这些具体实现中挑选一个耗时最小的算法实现,怎么挑选,下面简单说下:
实际上SparkPlanner对优化后的逻辑计划进行转换,是生成了多个可以执行的物理计划Physical Plan;
接着CBO(基于代价优化)优化策略会根据Cost Model算出每个Physical Plan的代价,并选取代价最小的 Physical Plan作为最终的Physical Plan。
以上的步骤合起来,就是Catalyst优化器!
2.5、执行物理计划
在最终真正执行物理执行计划之前,还要进行 Preparations 规则处理,将SQL转化为DAG,最后调用 SparkPlan 的 execute(),执行物理计划计算 RDD。
final def execute(): RDD

本文详细介绍了SparkSQL的底层执行架构,特别是Catalyst优化器如何将SQL语句转化为可执行的RDD,涉及Parser、Analyzer、Optimizer的步骤,以及RBO和CBO优化策略。通过实例展示了谓词下推、列值裁剪和常量累加等优化手段。
最低0.47元/天 解锁文章
792

被折叠的 条评论
为什么被折叠?



