1 Spark SQL背景
Spark SQL是从Shark发展而来的。Shark为了实现Hive兼容,在HQL方面重用了Hive中的HQL的析、逻辑执行计划翻译和执行计划优化等逻辑可以近似认为仅将物理执行计划从MR作业替换成了Spark作业(辅以内存列式存储等各种和Hive关系不大的优化);同时依赖Hive MetaStore和Hive SerDe(用于兼容现有的各种Hive存储格式)。
Spark SQL在Hive兼容层面仅依赖HQL parser、Hive MetaStore和Hive SerDe。也就是说,从HQL被解析成抽象语法树(AST)起,就全部由Spark SQL接管了。执行计划生成个优化都由Catalyst负责。借助Scala的模式匹配等函数式语言特性,利用Catalyst开发执行计划优化策略比Hive要简洁得多。
2 Spark SQL的整体实现框架
2.1 SQL语句的模块解析
当我们写一个查询语句时,一般包含三个部分,select部分、from数据源部分和where限制条件部分,这三部分的内容在SQL中有专门的名称:
当我们写SQL时,如上图所示,在进行逻辑解析时会把SQL分成三部分,Projection、DataSource和Filter模块,当生成执行部分时又把它们称为Result模块、DataSource模块和Operation模块。
在关系型数据库中,当我们写完一个查询语句进行执行时,发生的过程如下图所示:
(1)写完SQL查询语句,SQL查询引擎首先把我们的查询语句进行解析,也就是Parse过程,解析的过程是把我们写的查询语句进行分割,把Projection、DataSource和Filter三部分解析出来从而形成一个逻辑解析tree,在解析的过程中还会检查我们的SQL语法是否有错误,比如缺少指标字段、数据库中不包含这张数据表等。当发现有错误时立即停止解析,并报错。当顺利完成解析时,会进入到Bind过程。
(2)Bind(绑定)过程。把Parse过程后形成的逻辑解析tree,与数据库的数据字典绑定的过程。绑定后会形成一个执行tree,从而让程序知道表在哪里,需要什么字段等等。
(3)完成了Bind过程后,数据库查询引擎会提供几个查询执行计划,并且给出了查询执行计划的一些统计信息,既然提供了几个执行计划,那么有比较就有优劣,数据库会根据这些执行计划的统计信息选择一个最优的执行计划,因此这个过程是Optimize(优化)过程。
(4)选择了一个最优的执行计划,那么就剩下最后一步执行Execute,最后执行的过程和我们解析的过程是不一样的,当我们知道执行的顺序,对我们以后写SQL以及优化都是有很大的帮助的。执行查询后,先执行where部分,然后找到数据源之数据表,最后生成select的部分,得到最终结果。执行的顺序是:Operation->DataSource->Result。
2.2 Spark SQL框架的架构
使用Spark SQL一般的流程如下图所示:
如上图所示,一般情况下分为两个部分:把数据读入到Spark SQL中,Spark SQL进行数据处理或者算法实现,然后再把处理后的数据输出到相应的输出源中。
(1)同样我们也是从如果让我们开发,我们应该怎么做,需要考虑什么问题来思考这个问题。
① 第一个问题是,数据源有几个,我们可能从哪些数据源读取数据?现在Spark SQL支持很多的数据源,比如:hive数据仓库、json文件,.txt,以及orc文件,同时现在还支持jdbc从关系数据库中取数据。功能很强大。
② 还有一个需要思考的问题就是数据类型怎么映射?我们知道当我们从一个数据库表中读入数据时,我们定义的表结构的字段的类型和编程语言比如scala中的数据类型映射关系是怎样的一种映射关系?在Spark SQL中有解决这个问题的方法来实现表中的字段类型到编程语言数据类型的映射关系。
③ 数据有了,那么在Spark SQL中我们应该怎么组织这些数据,需要什么样的数据结构,同时我们对这些数据都可以进行什么样的操作?Spark SQL采用的是DataFrame数据结构来组织读入到Spark SQL中的数据,DataFrame数据结构其实和数据库的表结构差不多,数据是按照行来进行存储,同时还有一个schema,就相当于数据库的表结构,记录着每一行数据属于哪个字段。
④ 当数据处理完以后,我们需要把数据放入到什么地方,并切以什么样的格式进行对应,这与a和b要解决的问题是相同的。
(2)Spark SQL对于以上问题的实现逻辑也很明确,从上图已经很清楚,主要分为两个阶段,每个阶段都对应一个具体的类来实现。
① 对于第一个阶段,Spark SQL中存在两个类来解决这些问题:HiveContext和SQLContext:HiveContext继承了SQLContext的所有方法,并对其进行了扩展。因为我们知道, Hive和MySQL的查询还是有一定的差别的。HiveContext只是用来处理从Hive数据仓库中读入数据的操作,SQLContext可以处理Spark SQL能够支持的剩下的所有的数据源。这两个类处理的粒度是限制在对数据的读写和对表级别的操作上,比如,读入数据、缓存表、释放缓存表表、注册表、删除注册的表、返回表的结构等的操作。
② Spark SQL处理读入的数据,采用的是DataFrame中提供的方法。因为当我们把数据读入到Spark SQL中,这个数据就是DataFrame类型的。同时数据都是按照Row进行存储的。其中 DataFrame中提供了很多有用的方法。
③ 在Spark 1.6版本以后,又增加了一个类似于DataFrame的数据结构DataSet,增加此数据结构的目的DataFrame有软肋,他只能处理按照Row进行存储的数据,并且只能使用DataFrame中提供的方法,我们只能使用一部分RDD提供的操作。实现DataSet的目的就是让我们能够像操作RDD一样来操作Spark SQL中的数据。
④ 其中还有一些其他的类,但是现在在Spark SQL中最主要的就是上面的三个类,其他类以后碰到了会慢慢想清楚。
2.3 Spark SQL的HiveContext和SQLContext的运行原理
HiveContext和SQLContext与2.1部分讲到的SQL语句的模块解析实现的原理其实是一样的,采用了同样的逻辑过程。
(1)SQLContext的过程
在整个运行过程中涉及到多个SparkSQL的组件,如SqlParse、analyzer、optimizer、SparkPlan等等。
① SQL语句经过SqlParse解析成UnresolvedLogicalPlan;
② 使用analyzer结合数据数据字典(catalog)进行绑定,生成resolvedLogicalPlan;
③ 使用optimizer对resolvedLogicalPlan进行优化,生成optimizedLogicalPlan;
④ 使用SparkPlan将LogicalPlan转换成PhysicalPlan;
⑤ 使用prepareForExecution()将PhysicalPlan转换成可执行物理计划;
⑥ 使用execute()执行可执行物理计划;
⑦ 生成SchemaRDD。
(2)HiveContext的过程
① SQL语句经过HiveQl.parseSql解析成Unresolved LogicalPlan,在这个解析过程中对hiveql语句使用getAst()获取AST树,然后再进行解析;
② 使用analyzer结合数据hive源数据Metastore(新的catalog)进行绑定,生成resolved LogicalPlan;
③ 使用optimizer对resolved LogicalPlan进行优化,生成optimized LogicalPlan,优化前使用了ExtractPythonUdfs(catalog.PreInsertionCasts(catalog.CreateTables(analyzed)))进行预处理;
④ 使用hivePlanner将LogicalPlan转换成PhysicalPlan;
⑤ 使用prepareForExecution()将PhysicalPlan转换成可执行物理计划;
⑥ 使用execute()执行可执行物理计划;
⑦ 执行后,使用map(_.copy)将结果导入SchemaRDD。
参考文章:
[1] http://www.raincent.com/content-85-12996-1.html
[2] https://blog.51cto.com/9269309/1845525