文章目录
第1章 Spark SQL概述
1.1 什么是Spark SQL
Spark SQL是Spark用于结构化数据(structured data)处理的Spark模块。
与基本的Spark RDD API不同,Spark SQL的抽象数据类型为Spark提供了关于数据结构和正在执行的计算的更多信息。
在内部,Spark SQL使用这些额外的信息去做一些额外的优化,有多种方式与Spark SQL进行交互,比如: SQL和DatasetAPI。
当计算结果的时候,使用的是相同的执行引擎,不依赖你正在使用哪种API或者语言。这种统一也就意味着开发者可以很容易在不同的API之间进行切换,这些API提供了最自然的方式来表达给定的转换。
我们已经学习了Hive,它是将Hive SQL转换成 MapReduce然后提交到集群上执行,大大简化了编写 MapReduce的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。所以Spark SQL的应运而生,它是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快!
Spark SQL它提供了2个编程抽象,类似Spark Core中的RDD
-
DataFrame
-
DataSet
1.2 Spark SQL的特点
1.2.1 易整合
无缝的整合了 SQL 查询和 Spark 编程
1.2.2 统一的数据访问方式
使用相同的方式连接不同的数据源
1.2.3 兼容Hive
在已有的仓库上直接运行 SQL 或者 HiveQL
1.2.4 标准的数据连接
通过 JDBC 或者 ODBC 来连接
1.3 什么是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的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。
1.4 什么是DataSet
DataSet是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda函数的能力)以及Spark SQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作map,flatMap,filter等等)。
-
是DataFrame API的一个扩展,是SparkSQL最新的数据抽象
-
用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性;
-
用样例类来定义DataSet中数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称;
-
DataSet是强类型的。比如可以有DataSet[Car],DataSet[Person]。
-
DataFrame是DataSet的特列,DataFrame=DataSet[Row] ,所以可以通过as方法将DataFrame转换为DataSet。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息都用Row来表示。
第2章 Spark SQL编程
2.1 SparkSession新的起始点
在老的版本中,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
2.2 DataFrame
Spark SQL的DataFrame API 允许我们使用 DataFrame 而不用必须去注册临时表或者生成SQL表达式。DataFrame API 既有transformation操作也有action操作,DataFrame的转换从本质上来说更具有关系, 而 DataSet API 提供了更加函数式的 API
2.2.1 创建DataFrame
在Spark SQL中SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建;从一个存在的RDD进行转换;还可以从Hive Table进行查询返回。
1)从Spark数据源进行创建
查看Spark支持创建文件的数据源格式
scala> spark.read.
csv jdbc load options parquet table textFile
format json option orc schema text
读取json文件创建DataFrame
scala> spark.read.json("/root/users.json")
res0: org.apache.spark.sql.DataFrame = [Age: bigint, name: string]
注意:如果从内存中获取数据,spark可以知道数据类型具体是什么,如果是数字,默认作为Int处理;但是从文件中读取的数字,不能确定是什么类型,所以用bigint接收,可以和Long类型转换,但是和Int不能进行转换
2)从RDD进行转换
查看下文
3)Hive Table进行查询返回
查看下文
2.2.2 SQL风格语法
SQL语法风格是指我们查询数据的时候使用SQL语句来查询,这种风格的查询必须要有临时视图或者全局视图来辅助
object SQLDemo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
val df: DataFrame = spark.read.json("in/users.json")
df.createTempView("users")
spark.sql("select * from users").show()
}
}
注意:普通临时表是Session范围内的,如果想应用范围内有效,可以使用全局临时表。使用全局临时表时需要全路径访问,如:global_temp.users
df.createGlobalTempView("users")
spark.sql("select * from global_temp.users").show()
2.2.3 DSL风格语法
DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据,可以在 Scala, Java, Python 和 R 中使用 DSL,使用 DSL 语法风格不必去创建临时视图了
object SQLDemo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
val df: DataFrame = spark.read.json("in/users.json")
df.printSchema()
//查询name
df.select("name").show()
//查看所有列
df.select("*").show()
//查看name数据以及age+1数据
// 注意:涉及到运算的时候, 每列都必须使用$ 但是idea报错 spark-shell可以通过
df.select(df("name"),df("age")+1).show()
//查看”age”大于”19”的数据
df.filter(df("age")>19).show()
//按照”age”分组,查看数据条数
df.groupBy("age").count().show()
}
}
2.2.4 RDD转换为DataFrame
注意:如果需要RDD与DF或者DS之间操作,那么都需要引入 import spark.implicits._ (spark不是包名,而是sparkSession对象的名称,所以必须先创建SparkSession对象再导入. implicits是一个内部object)
- 前置条件
导入隐式转换并创建一个RDD
在某个目录下准备people.txt
zangsan,12
lisi,22
wangwu,25
val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
val sc = new SparkContext(conf)
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
val rdd: RDD[String] = sc.textFile("in/people.txt")
- 通过手动确定转换
rdd.map(_.split(","))
.map(x=>(x(0),x(1).toInt))
.toDF("name","age")
.show()
- 通过样例类反射转换(常用)
创建一个样例类
case class People(name:String,age:Int)
根据样例类将RDD转换为DataFrame
rdd.map(_.split(","))
.map(x=>People(x(0),x(1).toInt))
.toDF().show()
- 通过编程的方式(了解,一般编程直接操作RDD较少,操作hive或数据文件等较多)
package SQL
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{
IntegerType, StringType, StructField, StructType}
import org.apache.spark.{
SparkConf, SparkContext}
import org.apache.spark.sql.{
DataFrame, Row, SparkSession}
object SQLDemo {
case class People(name:String,age:Int)
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf().setAppName("sql").setMaster("local[3]")
val sc = new SparkContext(conf)
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
import spark.implicits._
val rdd: RDD[(String, Int)] = sc.makeRDD(Array(("lisi",20),("wangwu",32),("zs",36)))
// 映射出来一个 RDD[Row], 因为 DataFrame其实就是 DataSet[Row]
val rowRdd: RDD[Row] = rdd.map(x=>Row(x._1,x._2))
//创建StructType类型
val schema = StructType(Array(
StructField("name", StringType),
StructField("age", IntegerType)
))
val df: DataFrame = spark.createDataFrame(rowRdd,schema)
df.show()
}
}
2.2.5 DataFrame转换为RDD
直接调用rdd即可
df.rdd.collect().foreach(println)
注意:得到的RDD存储类型为Row
2.3DataSet
DataSet是具有强类型的数据集合,需要提供对应的类型信息。
2.3.1 创建DataSet
1)使用样例类序列创建DataSet
scala> case class Person(name:String,age:Long)
defined class Person
scala> val ds=Seq(Person("wangwu",21),Person("lisi",20)).toD
toDF toDS
scala> val ds=Seq(Person("wangwu",21),Person("lisi",20)).toDS
ds: org.apache.spark.sql.Dataset[Person] = [name: string, age: bigint]
scala> ds.show()
+------+---+
| name|age|
+------+---+
|wangwu| 21|
| lisi| 20|
+------+---+
2)使用基本类型的序列创建DataSet
scala> val ds=Seq(1,2,3,4,5).toDS
ds: org.apache.spark.sql.Dataset[Int] = [value: int]
scala> ds.show()
+-----+
|value|
+-----+
| 1|
| 2|
| 3|
| 4|
| 5|
+-----+
注意:在实际使用的时候,很少用到把序列转换成DataSet,更多是通过RDD来得到DataSet
2.3.2 RDD转换为DataSet
SparkSQL能够自动将包含有样例类的RDD转换成DataSet,样例类定义了table的结构,样例类属性通过反射变成了表的列名。样例类可以包含诸如Seq或者Array等复杂的结构。
scala> val rdd=sc.textFile(&#