Spark SQL精华及与Hive的集成
一.Spark SQL原理
1.SQL on Hadoop
- SQL是一种传统的用来进行数据分析的标准
Hive是原始的SQL-on-Hadoop解决方案
Impala:和Hive一样,提供了一种可以针对已有Hadoop数据编写SQL查询的方法
Presto:类似于Impala,未被主要供应商支持
Shark:Spark SQL的前身,设计目标是作为Hive的一个补充
Phoenix:基于HBase的开源SQL查询
2.spark的前身:shark
Shark的初衷:让hive运行在Spark上
是对HIve的改造,继承了大量的hive代码,给优化和维护带来了大量的麻烦
3.Spark SQL架构
- Spark SQL是Spark的核心组件之一(2014.4 Spark1.0)
- 能够直接访问现存的Hive数据
- 提供JDBC/ODBC接口供第三方工具借助Spark进行数据处理
- 提供了更高层级的接口方便地处理数据
- 支持多种操作方式:SQL、API编程
- 支持多种外部数据源:Parquet、JSON、RDBMS等
4.Spark SQL运行原理
Catalyst优化器是Spark SQL的核心
sql—逻辑计划树—逻辑计划—优化后的逻辑计划—物理计划—查询物理计划—RDD
Catalyst Optimizer:Catalyst优化器,将逻辑计划转为物理计划
5.Catalyst优化器
(1)逻辑计划
--在people表中找到id为1的人名字
SELECT name FROM
(
SELECT id, name FROM people
) p
WHERE p.id = 1
逻辑计划:Scan(people)—>Project(id,name)—>Filter(id=1)—>Project(name)
(2)优化
1、在投影上面查询过滤器
2、检查过滤是否可下压
(检查是否先进行过滤,减少数据处理,提高速度)
原计划:Scan(people)—>Project(id,name)—>Filter(id=1)—>Project(name)
Filter下压:Scan(people)—>Filter(id=1)—>Project(id,name)—>Project(name)
(3)物理计划
原计划:Scan(people)—>Project(id,name)—>Filter(id=1)—>Project(name)
Filter下压:Scan(people)—>Filter(id=1)—>Project(id,name)—>Project(name)
合并Projection:Scan(people)—>Filter(id=1)—>Project(name)
物理计划:IndexLoopup return:name
Catalyst进行一个逻辑计划到物理计划的优化,减少代码量
二.Spark SQL API
- SparkContext
- SQLContext
Spark SQL的编程入口 - HiveContext
SQLContext的子集,包含更多功能 - SparkSession(Spark 2.x推荐)
SparkSession:合并了SQLContext与HiveContext
提供与Spark功能交互单一入口点,并允许使用DataFrame和Dataset API对Spark进行编程 - Dataset (Spark 1.6+)
- DataFrame
1.Dataset
(1)基本概念
特定域对象中的强类型集合
由上图可知,直接启动spark-shell后会自动创建spark session对象,并命名为spark,所以下面可以直接调用
scala> spark.createDataset(1 to 3).show
+-----+
|value|
+-----+
| 1|
| 2|
| 3|
+-----+
scala> spark.createDataset(List("a","b")).show
+-----+
|value|
+-----+
| a|
| b|
+-----+
scala> spark.createDataset(List(("a",1),("b",2))).show
+---+---+
| _1| _2|
+---+---+
| a| 1|
| b| 2|
+---+---+
scala> spark.createDataset(sc.makeRDD(List((1,2,3,4),(2,3,4,5)))).show
+---+---+---+---+
| _1| _2| _3| _4|
+---+---+---+---+
| 1| 2| 3| 4|
| 2| 3| 4| 5|
有结构
- createDataset()的参数可以是:Seq、Array、RDD
- 上面三行代码生成的Dataset分别是:
Dataset[Int]、Dataset[(String,Int)]、Dataset[(String,Int,Int)] - Dataset=RDD+Schema,所以Dataset与RDD有大部共同的函数,如map、filter等
(2)使用Case Class创建Dataset
package lianxi22
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{
Dataset, SparkSession}
object CreateDataSetDemo {
//创建样例类
case class Point(label:String,x:Double,y:Double)
def main(args: Array[String]): Unit = {
//TODO:1.创建一个spark session对象
val spark: SparkSession = SparkSession.builder().master("local[*]")
.appName("test02")
.getOrCreate()
spark
//导包(spark中的一些方法)
import spark.implicits._
val sc: SparkContext = spark.sparkContext
//TODO:2.创建一个RDD
val pointRDD: RDD[(String, Double, Double)] = sc.makeRDD(List(("bar",3.0,4.0),("foo",2.0,2.5)))
//TODO:3.使用spark对象来创建Dataset
val ds1: Dataset[(String, Double, Double)] = pointRDD.toDS()
ds1.show()
//使用RDD使用样例类的方式生产Dataset
val pointDS: Dataset[Point] = pointRDD.map(x=>Point(x._1,x._2,x._3)).toDS()
pointDS.show()
}
}
//输出
+---+---+---+
| _1| _2| _3|
+---+---+---+
|bar|3.0|4.0|
|foo|2.0|2.5|
+---+---+---+
+-----+---+---+
|label| x| y|
+-----+---+---+
| bar|3.0|4.0|
| foo|2.0|2.5|
+-----+---+---+
对比可知:使用样例类后会变量成为列名
使用Dataset完成orders.csv表的数据加载
package lianxi22
import org.apache.spark.SparkContext
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.{
Dataset, SparkSession}
object Demo01 extends App {
val spark: SparkSession= SparkSession.builder()
.master("local[*]")
.getOrCreate()
import spark.implicits._
val sc: SparkContext = spark.sparkContext
val orderFile: RDD[String] = sc.textFile("file:///D:\\Users\\86188\\kb09\\spark1\\src\\data\\orders.csv")
private val orderDS: Dataset[Order] = orderFile.map(x => {
//"1","2013-07-25 00:00:00","11599","CLOSED"
//按逗号分隔,并去掉引号
val fields: Array[String] = x.split(",").map(y => y.replace("\"", ""))
Order(fields(0), fields(1), fields(2), fields(3))
}).toDS()
orderDS.show()
}
case class Order(id:String,date:String,customerId:String,status:String)
2.Dataframe
(1)基本概念
- DataFrame=Dataset[Row]
- 类似传统数据的二维表格
- 在RDD基础上加入了Schema(数据结构信息)
- DataFrame Schema支持嵌套数据类型
struct
map
array - 提供更多类似SQL操作的API
(2)RDD与DataFrame对比
(3)创建DataFrame及基本操作
package lianxi22
import org.apache.spark.sql.{
DataFrame, SparkSession}
object CreateDataFrame extends App {
//TODO:1.创建一个spark session对象
val spark: SparkSession = SparkSession.builder().master("local[*]")
.appName("test02")
.getOrCreate()
//导包(spark中的一些方法)
import spark.implicits._
//TODO:2.通过spark read读取json文件,创建dataframe
private val jsonDF: DataFrame = spark.read.json("file:///D:\\Users\\86188\\kb09\\spark1\\src\\data\\users.json")
//TODO:3.DataFrame常用操作
// 使用show方法将DataFrame的内容输出
jsonDF.show()
// 使用printSchema方法输出DataFrame的Schema信息
jsonDF.printSchema()
// 使用select方法来选择我们所需要的字段
jsonDF.select("name").show()
jsonDF.select(jsonDF("name"),jsonDF("Age")).show()
// 使用select方法选择我们所需要的字段,并为age字段加1
jsonDF.select(jsonDF("name"),jsonDF("Age")+1).show()
jsonDF.select($"name",$"Age").show()
// 使用filter方法完成条件过滤
jsonDF.filter($"Age">21).show()
//使用groupBy进行分组,求分组后的总数
jsonDF.groupBy("Age").count().show()
//sql()方法执行SQL查询操作
//把DataFrame注册成一张临时表
jsonDF.createOrReplaceTempView("people")
private val df2: DataFrame = spark.sql("select * from people")
df2.show()
spark.stop()
}
//输出
+----+-------+
| Age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
root
|-- Age: long (nullable = true)
|-- name: string (nullable = true)
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
+-------+----+
| name| Age|
+-------+----+
|Michael|null|
| Andy| 30|
| Justin| 19|
+-------+----+
+-------+---------+
| name|(Age + 1)|
+-------+---------+
|Michael| null|
| Andy| 31|
| Justin| 20|
+-------+---------+
+--