spark 系列
Spark SQL 详解
前言
看了前面的几篇 Spark 博客,相信大家对于 Spark 的基础框架、RDD和核心 SparkCore 已经搞明白了。本篇博客将为大家详细介绍了 Spark 一个重要的内置模块 Spark SQL 。这是工作中最常用的一个内置模块之一。
Spark SQL 简介
什么是Spark SQL?
Spark SQL是Spark用来处理结构化数据的一个模块,它提供了2个编程抽象:DataFrame和DataSet,并且作为分布式SQL查询引擎的作用。
我们已经学习了Hive,它是将Hive SQL转换成MapReduce然后提交到集群上执行,大大简化了编写MapReduce的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。所以Spark SQL的应运而生,它是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快!同时Spark SQL也支持从Hive中读取数据。
Spark SQL 的由来
SQL是一种传统的用来进行数据分析的标准,为了解决SQL on hadoop的问题,早期有以下几种方案:
- Hive是原始的SQL-on-Hadoop解决方案
- Impala:和Hive一样,提供了一种可以针对已有Hadoop数据编写SQL查询的方法
- Presto:类似于Impala,未被主要供应商支持
- Shark:Spark SQL的前身,设计目标是作为Hive的一个补充
- Phoenix:基于HBase的开源SQL查询引擎
Shark的初衷:让Hive运行在Spark之上。Spark 是对Hive的改造,继承了大量Hive代码,给优化和维护带来了大量的麻烦。
Spark SQL 的特点
1. 易整合(集成)
2. 统一的数据访问方式
3. 兼容Hive
4. 标准的数据连接
Spark SQL 框架结构
如上图所示,Spark SQL有以下主要功能:
- Spark SQL是Spark的核心组件之一(2014.4 Spark1.0)
- 能够直接访问现存的Hive数据
- 提供JDBC/ODBC接口供第三方工具借助Spark进行数据处理
- 提供了更高层级的接口方便地处理数据
- 支持多种操作方式:SQL、API编程
- 支持多种外部数据源:Parquet、JSON、RDBMS等
Spark SQL的核心 Catalyst优化器(了解)
Catalyst优化器是Spark SQL的核心。
在 深入研究Spark SQL的Catalyst优化器(原创翻译) 对Spark SQL的核心 Catalyst优化器的原理写的很清楚,这里我就简单的说一下 Catalyst优化器 优化一条SQL语句的大致流程。
例如,如下一条简单的SQL语句,Catalyst优化器是如何去优化的呢?
SELECT name FROM
(
SELECT id, name FROM people
) p
WHERE p.id = 1
如下图所示,Catalyst优化器的优化主要在投影上面查询过滤器并检查过滤是否可下压。一句话概括来说,Catalyst优化器的优化会自动选择最优执行效率的方案,这在过滤删选操作是非常实用的,可以减少hive SQL所必要的子查询嵌套,使代码变得简单一点。
Spark SQL 常用API
SparkContext 与 SparkSession
在Spark 2.0
以前的版本中,SparkSQL提供两种SQL查询起始点:一个叫SQLContext,用于Spark自己提供的SQL查询编程入口;一个叫HiveContext,是SQLContext的子集,提供更多的功能,主要用于连接Hive的查询。
SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContext和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了sparkContext,所以计算实际上是由sparkContext完成的。SparkSession提供与Spark功能交互单一入口点,并允许使用DataFrame和Dataset API对Spark进行编程。
SparkSession 创建语法:
val spark = SparkSession.builder.master("master").appName("appName").getOrCreate()
注意:
- 无特殊说明时,下文中“spark”均指SparkSession实例;
- 如果是spark-shell下,会自动创建“sc”和“spark”;
- master通常为
local[*]
,制定开启CPU核数进行计算,*
可为任何整数,如果为*
本身,代表动用计算机现有可用CPU核数。
在spark的早期版本中,SparkContext是spark的主要切入点,由于RDD是主要的API,我们通过sparkcontext来创建和操作RDD。对于每个其他的API,我们需要使用不同的context。例如,对于Streming,我们需要使用StreamingContext;对于sql,使用sqlContext;对于Hive,使用hiveContext。但是随着DataSet和DataFrame的API逐渐成为标准的API,就需要为他们建立接入点。
SparkContext 创建语法:
val conf = new SparkConf().setMaster("master").setAppName("appName")
val sc = new SparkContext(conf)
创建sparkContext对象:主要用于读取需要处理的数据,封装在RDD集合中;调度jobs执行。
SparkSession内部封装了SparkContext,所以计算实际上是由SparkContext完成的。
DataFrame
与RDD类似,DataFrame也是一个分布式数据容器。然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema。同时,与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)。从API易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。
上图直观地体现了DataFrame和RDD的区别。左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得Spark SQL可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待,DataFrame也是懒执行的。性能上比RDD要高。
RDD是分布式的 Java对象的集合。DataFrame是分布式的Row对象的集合。DataFrame除了提供了比RDD更丰富的算子以外,更重要的特点是提升执行效率、减少数据读取以及执行计划的优化。
创建方式与使用
在Spark SQL中SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建;从一个存在的RDD进行转换;还可以从Hive Table进行查询返回。
1. 通过Spark的数据源进行创建
json.txt 源数据:
{
"name":"hubert","age":"100"}
{
"name":"rose","age":"10"}
{
"name":"jack","age":"39"}
{
"name":"henry","age":"34"}
val spark = SparkSession.builder().appName("test").master("local[*]").getOrCreate()
// 读取json文件
val df = spark.read.format("json").load("data/json.txt")
// 操作
df.select("name","age").show(false)
spark.close()
// 结果
+------+---+
|name |age|
+------+---+
|hubert|100|
|rose |10 |
|jack |39 |
|henry |34 |
+------+---+
2. 从RDD中转换
下面 RDD转换为DateFrame 会细说。
3. 从Hive Table进行查询返回
这个将在后面的博文中涉及到,这里暂且不谈。
SQL风格语法(主要)
object Test04 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local[*]").getOrCreate()
// 1)创建一个DataFrame
val df = spark.read.json("data/json.txt")
// 2)对DataFrame创建一个临时表
df.createOrReplaceTempView("people")
// 3)通过SQL语句实现查询全表
val sqlDF = spark.sql("SELECT * FROM people")
// 4)结果展示
sqlDF.show(false)
// 5)对于DataFrame创建一个全局表
df.createGlobalTempView("people")
// 6)通过SQL语句实现查询全表
spark.sql("select * from global_temp.people").show(false)
spark.close()
}
}
注意:临时表是Session范围的,Session退出后,表就失效了。如果想应用范围内仍有效,可以使用全局表。注意使用全局表时需要全路径访问,如:global_temp:people。
全局的临时视图存在于系统数据库 global_temp中,我们必须加上库名去引用它
DSL 风格语法 (次要)
object Test04 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local[*]").getOrCreate()
// 导入 sparkSession 对象spark的隐式类
import spark.implicits._
// 1)创建一个DataFrame
val df = spark.read.json("data/json.txt")
// 2)查看DataFrame的Schema信息
df.printSchema()
// 3)只查看"name"列数据
df.select("name").show(false)
// 4)查看"name"列数据以及"age+1"数据
df.select($"name",$"age" + 1).show(false)
// 5)查看"age"大于"21"的数据
df.filter($"age" >21).show(false)
// 6)按照"age"分组,查看数据条数
df.groupBy("age").count().show()
spark.close()
}
}
结果:
root
|-- age: string (nullable = true)
|-- name: string (nullable = true)
+------+
|name |
+------+
|hubert|
|rose |
|jack |
|henry |
+------+
+------+---------+
|name |(age + 1)|
+------+---------+
|hubert|101.0 |
|rose |11.0 |
|jack |40.0 |
|henry |35.0 |
+------+---------+
+---+------+
|age|name |
+---+------+