vlambda博客
学习文章列表

「大数据」(八十一)Spark之SparkSQL运行架构

【导读:数据是二十一世纪的石油,蕴含巨大价值,这是·情报通·大数据技术系列第[81]篇文章,欢迎阅读和收藏】

1 SparkSQL 的发展历程

1.1 Hive and Shark

SparkSQL 的前身是 Shark , Shark 是伯克利实验室 Spark 生态环境的组件之一,它修改了内存管理、物理计划、执行三个模块,并使之能运行在 Spark 引擎上,从而使得 SQL 查询的速度得到 10-100 倍的提升。


1.2 Shark 和 SparkSQL

随着 Spark 的发展,对于野心勃勃的 Spark 团队来说, Shark 对于 Hive 的太多依赖(如采用 Hive 的语法解析器、查询优化器等等),制约了 Spark 的 One Stack Rule Them All 的既定方针,制约了 Spark 各个组件的相互集成,所以提出了 SparkSQL 项目。SparkSQL 抛弃原有 Shark 的代码,汲取了 Shark 的一些优点,如内存列存储( In-Memory Columnar Storage )、 Hive 兼容性等,重新开发了 SparkSQL 代码;由于摆脱了对 Hive 的依赖性, SparkSQL 无论在数据兼容、性能优化、组件扩展方面都得到了极大的方便,真可谓 “ 退一步,海阔天空 ” 。

2014 年 6 月 1 日 Shark 项目和 SparkSQL 项目的主持人 Reynold Xin 宣布:停止对 Shark 的开发,团队将所有资源放 SparkSQL 项目上,至此, Shark 的发展画上了句话,但也因此发展出两个直线:SparkSQL 和 Hive on Spark 。

其中 SparkSQL 作为 Spark 生态的一员继续发展,而不再受限于 Hive ,只是兼容 Hive ;而 Hive on Spark 是一个 Hive 的发展计划,该计划将 Spark 作为 Hive 的底层引擎之一,也就是说, Hive 将不再受限于一个引擎,可以采用 Map-Reduce 、 Tez 、 Spark 等引擎。


1.3 SparkSQL 的性能

SparkSQL 在下面几点做了优化:

1.3.1 内存列存储( In-Memory Columnar Storage )

SparkSQL 的表数据在内存中存储不是采用原生态的 JVM 对象存储方式,而是采用内存列存储。

该存储方式无论在空间占用量和读取吞吐率上都占有很大优势。

对于内存列存储来说,将所有原生数据类型的列采用原生数组来存储,将 Hive 支持的复杂数据类型(如 array 、 map 等)先序化后并接成一个字节数组来存储。这样,每个列创建一个 JVM 对象,从而导致可以快速的 GC 和紧凑的数据存储;额外的,还可以使用低廉 CPU 开销的高效压缩方法(如字典编码、行长度编码等压缩方法)降低内存开销;更有趣的是,对于分析查询中频繁使用的聚合特定列,性能会得到很大的提高,原因就是这些列的数据放在一起,更容易读入内存进行计算。

1.3.2 字节码生成技术( bytecode generation ,即 CG )

在数据库查询中有一个昂贵的操作是查询语句中的表达式,主要是由于 JVM 的内存模型引起的。比如如下一个查询:

SELECT a + b FROM table

在这个查询里,如果采用通用的 SQL 语法途径去处理,会先生成一个表达式树(有两个节点的 Add 树,参考后面章节),在物理处理这个表达式树的时候,将会如图所示的 7 个步骤:

1. 调用虚函数 Add.eval() ,需要确认 Add 两边的数据类型

2. 调用虚函数 a.eval() ,需要确认 a 的数据类型

3. 确定 a 的数据类型是 Int ,装箱

4. 调用虚函数 b.eval() ,需要确认 b 的数据类型

5. 确定 b 的数据类型是 Int ,装箱

6. 调用 Int 类型的 Add

7. 返回装箱后的计算结果

其中多次涉及到虚函数的调用,虚函数的调用会打断 CPU 的正常流水线处理,减缓执行。

Spark1.1.0 在 catalyst 模块的 expressions 增加了 codegen 模块,如果使用动态字节码生成技术(配置 spark.sql.codegen 参数), SparkSQL 在执行物理计划的时候,对匹配的表达式采用特定的代码,动态编译,然后运行。

2 SparkSQL 运行架构

类似于关系型数据库, SparkSQL 也是语句也是由 Projection ( a1 , a2 , a3 )、 Data Source ( tableA )、 Filter ( condition )组成,分别对应 sql 查询过程中的 Result 、 Data Source 、 Operation ,也就是说 SQL 语句按 Result-->Data Source-->Operation 的次序来描述的。

当执行 SparkSQL 语句的顺序为:

1 、对读入的 SQL 语句进行解析( Parse ),分辨出 SQL 语句中哪些词是关键词(如 SELECT 、 FROM 、 WHERE ),哪些是表达式、哪些是 Projection 、哪些是 Data Source 等,从而判断 SQL 语句是否规范;

2 、将 SQL 语句和数据库的数据字典(列、表、视图等等)进行绑定( Bind ),如果相关的 Projection 、 Data Source 等都是存在的话,就表示这个 SQL 语句是可以执行的;

3 、一般的数据库会提供几个执行计划,这些计划一般都有运行统计数据,数据库会在这些计划中选择一个最优计划( Optimize );

4 、计划执行( Execute ),按 Operation-->Data Source-->Result 的次序来进行的,在执行过程有时候甚至不需要读取物理表就可以返回结果,比如重新运行刚运行过的 SQL 语句,可能直接从数据库的缓冲池中获取返回结果。