SparkSQL的应用与使用
SparkSQL
的前身是Shark
,给熟悉RDBMS
但又不理解MapReduce
的技术人员提供快速上手的工具。
Hive
是早期唯一运行在Hadoop
上的SQL-on-Hadoop
工具。但是MapReduce
计算过程中大量的中间磁盘落地过程消耗了大量的 I/O,降低的运行效率,为了提高SQL-on-Hadoop
的效率,大量的SQL-on-Hadoop
工具开始产生,其中表现较为突出的是:
- Drill
- Impala
- Shark
其中Shark
是伯克利实验室Spark
生态环境的组件之一,是基于Hive
所开发的工具,它修改了下图所示的右下角的内存管理、物理计划、执行三个模块,并使之能运行在Spark
引擎上。Shark
的出现,使得SQL-on-Hadoop
的性能比Hive
有了 10-100 倍的提高。
但是,随着Spark
的发展,对于野心勃勃的Spark
团队来说,Shark
对于Hive
的太多依赖(如采用Hive
的语法解析器、查询优化器等等),制约了Spark
的One Stack Rule Them All
的既定方针,制约了Spark
各个组件的相互集成,所以提出了SparkSQL
项目。SparkSQL
抛弃原有Shark
的代码,汲取了Shark
的一些优点,如内存列存储(In-Memory Columnar Storage
)、Hive
兼容性等,重新开发了SparkSQL
代码;由于摆脱了对Hive
的依赖性,SparkSQL
无论在数据兼容、性能优化、组件扩展方面都得到了极大的方便,真可谓“退一步,海阔天空”。
- 数据兼容方面
SparkSQL
不但兼容Hive
,还可以从RDD
、parquet
文件、JSON
文件中获取数据,未来版本甚至支持获取RDBMS
数据以及cassandra
等NOSQL
数据; - 性能优化方面 除了采取
In-Memory Columnar Storage
、byte-code generation
等优化技术外、将会引进Cost Model
对查询进行动态评估、获取最佳物理计划等等; -
组件扩展方面 无论是
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
等引擎。
对于开发人员来讲,SparkSQL
可以简化RDD
的开发,提高开发效率,且执行效率非常快,所以实际工作中,基本上采用的就是 SparkSQL
。Spark SQL
为了简化RDD
的开发,提高开发效率,提供了 2 个编程抽象,类似Spark Core
中的RDD
- DataFrame
- DataSet
SparkSQL的特点
易整合
-
无缝的整合了
SQL
查询和Spark
编程
统一的数据访问
-
使用相同的方式连接不同的数据源
兼容 Hive
-
在已有的仓库上直接运行
SQL
或者HiveQ
标准数据连接
-
通过
JDBC
或者ODBC
来连接
DataFrame和DataSet
DataFrame
在 Spark
中,DataFrame
是一种以 RDD
为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame
与RDD
的主要区别在于,前者带有 schema 元信息,即 DataFrame
所表示的二维表数据集的每一列都带有名称和类型。这使得 Spark SQL
得以洞察更多的结构信息,从而对藏于 DataFrame
背后的数据源以及作用于 DataFrame
之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观 RDD
,由于无从得知所存数据元素的具体内部结构,Spark Core
只能在 stage
层面进行简单、通用的流水线优化。
同时,与 Hive
类似,DataFrame
也支持嵌套数据类型(struct
、array
和map
)。从 API 易用性的角度上看,DataFrame API
提供的是一套高层的关系操作,比函数式的RDD API
要更加友好,门槛更低。
上图直观地体现了 DataFrame
和RDD
的区别。
左侧的 RDD[Person]
虽然以 Person
为类型参数,但 Spark
框架本身不了解 Person
类的内部结构。而右侧的 DataFrame
却提供了详细的结构信息,使得Spark SQL
可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。
DataFrame
是为数据提供了Schema
的视图。可以把它当做数据库中的一张表来对待
DataFrame
也是懒执行的,但性能上比 RDD
要高,主要原因:优化的执行计划,即查询计划通过 Spark catalyst optimiser
进行优化。比如下面一个例子:
为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame
,将它们 join
之后又做了一次filter
操作。如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为 join
是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter
下推到join
下方,先对 DataFrame
进行过滤,再 join
过滤后的较小的结果集,便可以有效缩短执行时间。而 Spark SQL
的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。
DataSet
DataSet
是分布式数据集合。DataSet
是 Spark 1.6
中添加的一个新抽象,是 DataFrame
的一个展。它提供了 RDD
的优势(强类型,使用强大的 lambda
函数的能力)以及 Spark SQL
优化执行引擎的优点。DataSet
也可以使用功能性的转换(操作 map
,flatMap
,filter
等等)。
DataSet
是DataFrame API
的一个扩展,是SparkSQL
最新的数据抽象- 用户友好的
API
风格,既具有类型安全检查也具有DataFrame
的查询优化特性; - 用样例类来对
DataSet
中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet
中的字段名称; DataSet
是强类型的。比如可以有DataSet[Car],DataSet[Person]
。DataFrame
是DataSet
的特列,DataFrame=DataSet[Row]
,所以可以通过as
方法将DataFrame
转换为DataSet
。Row
是一个类型,跟Car
、Person
这些的类型一样,所有的表结构信息都用Row
来表示。获取数据时需要指定顺序
SparkSQL核心编程
新的起点
Spark Core
中,如果想要执行应用程序,需要首先构建上下文环境对象 SparkContext
,Spark SQL
其实可以理解为对 Spark Core
的一种封装,不仅仅在模型上进行了封装,上下文环境对象也进行了封装。
在老的版本中,SparkSQL
提供两种 SQL
查询起始点:一个叫SQLContext
,用于 Spark
自己提供的 SQL
查询;一个叫 HiveContext
,用于连接 Hive
的查询。
SparkSession
是 Spark
最新的 SQL
查询起始点,实质上是SQLContext
和 HiveContext
的组合,所以在 SQLContex
和HiveContext
上可用的API
在 SparkSession
上同样是可以使用的。SparkSession
内部封装了 SparkContext
,所以计算实际上是由 sparkContext
完成的。当我们使用 spark-shell
的时候, spark 框架会自动的创建一个名称叫做 spark
的 SparkSession
对象, 就像我们以前可以自动获取到一个sc
来表示 SparkContext
对象一样
DataFrame的创建
在 Spark SQL
中 SparkSession
是创建 DataFrame
和执行 SQL
的入口,创建 DataFrame
有三种方式:通过 Spark
的数据源进行创建;从一个存在的RDD
进行转换;还可以从数据库中读取 进行查询返回df。
从数据源进行创建
//创建SparkSession
val spark = SparkSession.builder()
.master("local[6]")
.appName("reader1")
.getOrCreate()
//框架
//第一种读取形式
spark.read
.format("csv")
.option("header", true)
.option("inferSchema", true) //文件的数据类型(每一行)
.load("data/BeijingPM20100101_20151231.csv")
.show()
//第二种读取形式
spark.read
.option("header", true)
.option("inferSchema", true) //文件的数据类型(每一行)
.csv("data/BeijingPM20100101_20151231.csv")
.show()
结果展示
从一个存在RDD衍生
- 在 IDEA 中开发程序时,如果需要 RDD 与 DF 或者 DS 之间互相操作,那么需要引入
import spark.implicits._
- 这里的
spark
不是Scala
中的包名,而是创建的sparkSession
对象的变量名称,所以必须先创建SparkSession
对象再导入。这里的spark
对象不能使用var
声明,因为Scala
只支持val
修饰的对象的引入。
@Test
def createDataFrame02() = {
//隐式转换
import spark.implicits._
val df = Seq(
(1, "First Value", java.sql.Date.valueOf("2010-01-01")),
(2, "Second Value", java.sql.Date.valueOf("2010-02-01"))
).toDF("int_column", "string_column", "date_column").show()
}
结果展示
数据库读取
@Test
def createDataFrame03() = {
spark.read
.format("jdbc")
.option("url", "jdbc:mysql://127.0.0.1:3306/spark")
.option("dbtable", "student")
.option("user", "root")
.option("password", "123456")
.load()
.show()
}
结果展示
DataSet的使用
DataSet 是具有强类型的数据集合,需要提供对应的类型信息。
使用样例类创建DataSet
- 样例类
case class Stu(Id: Int, name: String)
- 生成DS
@Test
def createDataSetTest01() = {
import spark.implicits._
//ds是具有类型的
val df: Dataset[Stu] = Seq(Stu(1, "zhangsan"), Stu(2, "lisi")).toDS
df.show()
}
结果展示
** 使用基本类型的序列创建 DataSet**
@Test
def createDataSetTest02() = {
import spark.implicits._
//ds是具有类型的
val df: Dataset[Int] = Seq(1,2,3,4).toDS
df.show()
}
结果展示
RDD、DataFrame、DataSet 三者的关系
在 SparkSQL 中 Spark 为我们提供了两个新的抽象,分别是 DataFrame 和 DataSet。他们和 RDD 有什么区别呢?首先从版本的产生上来看:
- Spark1.0 => RDD
- Spark1.3 => DataFrame
- Spark1.6 => Dataset
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。在后期的 Spark 版本中,DataSet 有可能会逐步取代 RDD和 DataFrame 成为唯一的 API 接口。
三者的共性
RDD
、DataFrame
、DataSet
全都是spark
平台下的分布式弹性数据集,为处理超大型数据提供便利;- 三者都有惰性机制,在进行创建、转换,如
map
方法时,不会立即执行,只有在遇到Action
如foreach
时,三者才会开始遍历运算; - 三者有许多共同的函数,如
filter
,排序等; - 在对
DataFrame
和Dataset
进行操作许多操作都需要这个包:import spark.implicits._
(在创建好SparkSession
对象后尽量直接导入) - 三者都会根据
Spark
的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出 - 三者都有
partition
的概念 DataFrame
和DataSet
均可使用模式匹配获取各个字段的值和类型
三者的区别
RDD
- RDD 一般和
spark mllib
同时使用 - RDD 不支持
sparksql
操作
DataFrame
- 与 RDD 和
Dataset
不同,DataFrame
每一行的类型固定为Row
,每一列的值没法直接访问,只有通过解析才能获取各个字段的值 DataFrame
与DataSet
一般不与spark mllib
同时使用DataFrame
与DataSet
均支持SparkSQL
的操作,比如select
,groupby
之类,还能注册临时表/视窗,进行 sql 语句操作DataFrame
与DataSet
支持一些特别方便的保存方式,比如保存成 csv,可以带上表头,这样每一列的字段名一目了然(后面专门讲解)
DataSet
Dataset
和DataFrame
拥有完全相同的成员函数,区别只是每一行的数据类型不同。DataFrame
其实就是DataSet
的一个特例type DataFrame = Dataset[Row]
DataFrame
也可以叫Dataset[Row]
,每一行的类型是Row
,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS
方法或者共性中的第七条提到的模式匹配拿出特定字段。而Dataset
中,每一行是什么类型是不一定的,在自定义了case class
之后可以很自由的获得每一行的信息
三者间的转换