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

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

1 SparkSQL 的发展历程

1.1 Hive and Shark

SparkSQL 的前身是 Shark ,给熟悉 RDBMS 但又不理解 MapReduce 的技术人员提供快速上手的工具, Hive 应运而生,它是当时唯一运行在 Hadoop 上的 SQL-on-Hadoop 工具。但是 MapReduce 计算过程中大量的中间磁盘落地过程消耗了大量的 I/O ,降低的运行效率,为了提高 SQL-on-Hadoop 的效率,大量的 SQL-on-Hadoop 工具开始产生,其中表现较为突出的是:

l MapR 的 Drill

l Cloudera 的 Impala

l Shark

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

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

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 无论在数据兼容、性能优化、组件扩展方面都得到了极大的方便,真可谓 “ 退一步,海阔天空 ” 。

l 数据兼容方面 不但兼容 Hive ,还可以从 RDD 、 parquet 文件、 JSON 文件中获取数据,未来版本甚至支持获取 RDBMS 数据以及 cassandra 等 NOSQL 数据;

l 性能优化方面 除了采取 In-Memory Columnar Storage 、 byte-code generation 等优化技术外、将会引进 Cost Model 对查询进行动态评估、获取最佳物理计划等等;

l 组件扩展方面 无论是 SQL 的语法解析器、分析器还是优化器都可以重新定义,进行扩展。

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 对象存储方式,而是采用内存列存储。

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

对于原生态的 JVM 对象存储方式,每个对象通常要增加 12-16 字节的额外开销,对于一个 270MB 的 TPC-H lineitem table 数据,使用这种方式读入内存,要使用 970MB 左右的内存空间(通常是 2 ~ 5 倍于原生数据空间);另外,使用这种方式,每个数据记录产生一个 JVM 对象,如果是大小为 200B 的数据记录, 32G 的堆栈将产生 1.6 亿个对象,这么多的对象,对于 GC 来说,可能要消耗几分钟的时间来处理( JVM 的垃圾收集时间与堆栈中的对象数量呈线性相关)。显然这种内存存储方式对于基于内存计算的 Spark 来说,很昂贵也负担不起。

对于内存列存储来说,将所有原生数据类型的列采用原生数组来存储,将 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 语句,可能直接从数据库的缓冲池中获取返回结果。


分享到:


相關文章: