日期 |
版本 |
修订 |
审批 |
修订说明 |
2016.10.20 |
1.0 |
章鑫8 |
|
初始版本 |
|
|
|
|
|
1 简介
SparkSQL是Spark的一个组件,用于结构化数据的计算,SparkSQL提供了一个称为DataFrames的编程抽象,DataFrames可以充当分布式SQL查询引擎。
与SparkSQL紧密相关的组件是Shark和hive,其中Shark已经被开发者摒弃。
2 开发背景
2.1 Shark和hive
SparkSQL的前身是Shark,给熟悉RDBMS但又不理解MapReduce的技术人员提供快速上手的工具,Hive应运而生,它是当时唯一运行在Hadoop上的SQL-on-Hadoop工具。但是MapReduce计算过程中大量的中间磁盘落地过程消耗了大量的I/O,降低的运行效率,为了提高SQL-on-Hadoop的效率,大量的SQL-on-Hadoop工具开始产生,其中表现较为突出的是:
Ø MapR的Drill
Ø Cloudera的Impala
Ø Shark
其中Shark是伯克利实验室Spark生态环境的组件之一,它修改了下图1所示的右下角的内存管理、物理计划、执行三个模块,并使之能运行在Spark引擎上,从而使得SQL查询的速度得到10-100倍的提升。
图1 hive和Shark框架
2.2 Shark和SparkSQL
随着Spark的发展,对于野心勃勃的Spark团队来说,Shark对于Hive的太多依赖(如
采用Hive的语法解析器、查询优化器等等),制约了Spark的One Stack Rule Them All的既定方针,制约了Spark各个组件的相互集成,所以提出了SparkSQL项目。SparkSQL抛弃原有Shark的代码,汲取了Shark的一些优点,如内存列存储(In-Memory ColumnarStorage)、Hive兼容性等,重新开发了SparkSQL代码;由于摆脱了对Hive的依赖性,SparkSQL无论在数据兼容、性能优化、组件扩展方面都得到了极大的方便。
Ø 数据兼容方面,不但兼容Hive,还可以从RDD、parquet文件、JSON文件中获取数据,未来版本甚至支持获取RDBMS数据以及cassandra等NOSQL数据;
Ø 性能优化方面,除了采取In-Memory ColumnarStorage、byte-codegeneration等优化技术外、将会引进CostModel对查询进行动态评估、获取最佳物理计划等等;
Ø 组件扩展方面,无论是SQL的语法解析器、分析器还是优化器都可以重新定义,进行扩展。
2014年6月1日Shark项目和SparkSQL项目的主持人Reynold Xin宣布:停止对Shark的开发,团队将所有资源放SparkSQL项目上,至此,Shark的发展画上了句话,但也因此发展出两个直线:SparkSQL和Hive on Spark。
图2
其中SparkSQL作为Spark生态的一员继续发展,而不再受限于Hive,只是兼容Hive;而Hive on Spark是一个Hive的发展计划,该计划将Spark作为Hive的底层引擎之一,也就是说,Hive将不再受限于一个引擎,可以采用Map-Reduce、Tez、Spark等引擎。
3 技术概念
相比于Spark RDD API,Spark SQL包含了对结构化数据和在其上的运算的更多的信息,
Spark SQL使用这些信息进行了额外的优化,使对结构化数据的操作更加高效和方便。
有多种方式去使用SparkSQL,包括SQL、DataFrames API和Datasets API。但无论是哪种API或者是编程语言,它们都是基于同样的执行引擎,因此你可以在不同的API之间随意切换,它们各有各的特点。
.
3.1 SQL
使用SparkSQL的一种方式就是通过SQL语句来执行SQL查询。当在编程语言中使用
SQL时,其返回结果将被封装为一个DATAFrames。
3.2 DataFrame
DataFrame是一个分布式集合,其中数据被组织为命名的列。它概念上等价于关系数
据库中的表,但底层做了更多的优化。DataFrame可以从很多数据源构建,比如:已经存在的RDD、结构化的文件、外部数据库、Hive表等。
DataFrame的前身是SchemaRDD,从Spark 1.3.0开始SchemaRDD更名为DataFrame。与SchemaRDD的主要区别是:DataFrame不再直接继承自RDD,而是自己实现了RDD的绝大多数功能。你仍旧可以在DataFrame上调用.rdd方法将其转换为一个RDD。RDD可看做是分布式的对象的集合,Spark并不知道对象的详细信息,DataFrame可看做是分布式的Row对象的集合,其提供了由列组成的详细信息,使得SparkSQL可以进行某些形式的执行优化。DataFrame和普通的RDD的逻辑框架区别如下所示:
图3 RDD和DataFrame
DataFrame不仅比RDD有更加丰富的算子,更重要的是它可以进行执行计划优化(得益于CatalystSQL解析器),另外Tungsten项目给DataFrame的执行效率带来了很大提升(不过Tungsten优化也可能在后续开发中加入到RDD API中)。
但是在有些情况下RDD可以表达的逻辑用DataFrame无法表达,所以后续提出了Dataset API,Dataset结合了RDD和DataFrame的好处。
3.3 Dataset
Dataset是Spark 1.6新添加的一个实验性接口,其目的是想结合RDD的好处(强类
型(这意味着可以在编译时进行类型安全检查)、可以使用强大的lambada函数)和SparkSQL的优化执行引擎的好处。可以从JVM对象构造出Dataset,然后使用类似于RDD的函数式转换算子(map/flatMap/filter等)对其进行操作。
Dataset通过Encoder实现了自定义的序列化格式,使得某些操作可以在无需序列化的情况下直接进行。另外Dataset还进行了包括Tungsten优化在内的很多性能方面的优化。
实际上Dataset是包含了DataFrame的功能的,这样二者就出现了很大的冗余,故Spark 2.0将二者统一:保留Dataset API,把DataFrame表示为Dataset[Row],即Dataset的子集。
3.4 API进化
Spark在迅速的发展,从原始的RDD API,再到DataFrame API,再到Dataset的出现,
执行性能上有了很大的提升。
我们在使用API时,应该优先选择DataFrames和Dataset,因为这二者的性能很好,而且以后的优化它都可以享受到,但是为了兼容早期版本的程序,RDD API也会一直保留着。后续Spark上层的库将全部会用DataFrames和Dataset,比如MLlib、Streaming、Graphx等。
3.5 SparkSQL的数据源
SparkSQL支持通过SchemaRDD接口操作各种数据源。一个SchemaRDD能够作为一个
一般的RDD被操作,也可以被注册为一个临时的表。注册一个SchemaRDD为一个表就可以允许你在其数据上运行SQL查询。
加载数据为SchemaRDD的多种数据源,包括RDDs、parquet文件(列式存储)、JSON数据集、Hive表,以下主要介绍将RDDs转换为schemaRDD的两种方法。
(1) 利用反射推断模式
使用反射来推断包含特定对象类型的RDD的模式(schema)。适用于写Spark程序的同
时,已经知道了模式,使用反射可以使得代码简洁。结合样本的名字,通过反射读取,作为列的名字。这个RDD可以隐式转化为一个schemaRDD,然后注册为一个表。表可以在后续的sql语句中使用。
val sqlContext = new org.apache.spark.sql.SQLContext(sc) import sqlContext.implicits._ case class Person(name:String,age:Int) val people = sc.textFile("file:///home/hdfs/people.txt").map(_.split(",")).map(p => Person(p(0),p(1).trim.toInt)).toDF() people.registerTempTable("people") val teenagers = sqlContext.sql("SELECT name,age FROM people WHERE age>= 19 AND age <=30") teenagers.map(t => "Name:"+t(0)).collect().foreach(println) teenagers.map(t => "Name:" + t.getAs[String]("name")).collect().foreach(println) teenagers.map(_.getValueMap[Any](List("name","age"))).collect().foreach(println) |
(2)编程指定模式
通过一个编程接口构造模式来实现,然后可在存在的RDDs上使用它。适用于当前样本模式未知一个SchemaRDD可以通过三步来创建。
Ø 从原来的RDD创建一个行的RDD
Ø 创建由一个StructType表示的模式与第一步创建的RDD的行结构相匹配
Ø 在行RDD上通过applySchema方法应用模式
val people = sc.textFile("file:///home/hdfs/people.txt") val schemaString = "name age" import org.apache.spark.sql.Row; import org.apache.spark.sql.types.{StructType,StructField,StringType}; val schema = StructType(schemaString.split(" ").map(fieldName => StructField(fieldName,StringType,true))) val rowRDD = people.map(_.split(",")).map(p => Row(p(0),p(1).trim)) val peopleSchemaRDD = sqlContext.applySchema(rowRDD,schema) peopleSchemaRDD.registerTempTable(" |