Spark SQL架构
- Spark SQL是Spark的核心组件之一(2014.4 Spark1.0)
- 能够直接访问现存的Hive数据
- 提供JDBC/ODBC接口供第三方工具借助Spark进行数据处理
- 提供了更高层级的接口方便地处理数据
- 支持多种操作方式:SQL、API编程
- 支持多种外部数据源:Parquet、JSON、RDBMS等
Spark SQL运行原理
Catalyst优化器是Spark SQL的核心,所有 SQL 操作最终都通过 Catalyst 翻译成类
似的 Spark 程序代码被 Spark Core 调度执行,其过程也有 Job、Stage、Task 的概
念。如下图所示。
- SQL 执行过程
对于 Spark SQL 来说,从 SQL 到 RDD 的执行需要经过两个大的阶段,分别
是逻辑计划和物理计划。Catalyst 优化器,作用便是将逻辑计划转为物理计划。
如下图所示。
SQL 执行过程-1
SQL 执行过程-2
Catalyst优化器
1.逻辑计划
逻辑计划阶段会将用户所写的 SQL 语句转换成树型数据结构(逻辑算子
树),SQL 语句中蕴含的逻辑映射到逻辑算子树的不同节点。顾名思义,逻辑计
划阶段生成的逻辑算子树并不会直接提交执行,仅作为中间阶段。最终逻辑算子
树的生成过程经历三个子阶段,分别对应:
1)未解析的逻辑算子树(Unresolved LogicaPlan ,仅仅是数据结构,不包
含任何数据信息等);
2)解析后的逻辑算子树(Analyzed LogicalPlan ,节点中绑定各种信息);
3)优化后的逻辑算子树(Optimized LogcalPlan ,应用各种优化规则对一些
低效的逻辑计划进行转换)。
物理计划阶段将上一步逻辑计划阶段生成的逻辑算子树进行进一步转换,生
成物理算子树。物理算子树的节点会直接生成 RDD 或对 RDD 进行
transformation 操作(注:每个物理计划节点中都实现了对 RDD 进行转换的
execute 方法)。同样地,物理计划阶段也包含三个子阶段:
1)首先,根据逻辑算子树,生成物理算子树的列表 Iterator[PhysicaPlan] (同
样的逻辑算子树可能对应多个物理算子树);
2)然后,从列表中按照一定的策略选取最优的物理算子树(SparkPlan );
3)最后,对选取的物理算子树进行提交前的准备工作,例如,确保分区操
作正确、物理算子树节点重用、执行代码生成等,得到“准备后”的物理算子树
(Prepared SparkPlan)。
经过上述步骤后,物理算子树生成的 RDD 执行 action 操作,即可提交执
行。
从 SQL 语句的解析一直到提交之前,上述整个转换过 Spark 集群 Driver
端进行,不涉及分布式环境。SparkSession 类的 sql 方法调用 SessionState 中的
各种对象,包括上述不同阶段对应的 SparkSqlParser 类、 Analyzer 类、
Optimizer SparkPlanner 类等,最后封装成 Query Execution 对象。因此,在进行
Spark SQL 开发时,可以很方便地将每一步生成的计划单独剥离出来分析。
如下图所示,左上角是 SQL 语句,生成的逻辑算子树中有 Relation、Filter、
Project 节点,分别对应数据 、过滤逻辑( age>18 )和列剪裁逻辑 。下一步
的物理算子树从逻辑算子树一对一映射得到,Relation 逻辑节点转换为
FileSourceScanExec 执行节点,Filter 逻辑节点转换为 FilterExec 执行节点,
Project 逻辑节点转换为 ProjectExec 执行节点。
2.优化
在投影上面查询过滤器
检查过滤是否可下压
3.物理计划
Dataset
特定域对象中的强类型集合
1、createDataset()的参数可以是:Seq、Array、RDD
2、上面三行代码生成的Dataset分别是:
Dataset[Int]、Dataset[(String,Int)]、Dataset[(String,Int,Int)]
3、Dataset=RDD+Schema,所以Dataset与RDD有大部共同的函数,如map、filter等
scala> spark.createDataset(1 to 3).show
scala> spark.createDataset(List(("a",1),("b",2),("c",3))).show
scala> spark.createDataset(sc.parallelize(List(("a",1,1),("b",2,2)))).show
使用Case Class创建Dataset
object Demo3 {
//创建样例类
case class Point1(label:String,x:Double,y:Double)
case class Category1(id:Long,name:String)
def main(args: Array[String]): Unit = {
// 创建一个SparkSession对象
val spark = SparkSession.builder().master("local[*]").appName("aa").getOrCreate()
val sc = spark.sparkContext
//DS的包
import spark.implicits._
//todo 创建一个RDD
val categoriesRDD=sc.parallelize(List((1,"foo"),(2,"bar")))
//ToDO 使用Spark对象来创建Dataset
val value: Dataset[sparksql.Demo3.Category1] = categoriesRDD.map(x=>Category1(x._1, x._2)).toDS()
value.show()
}
}
DataFrame
1.DataFrame=Dataset[Row]
2.类似传统数据的二维表格
3.在RDD基础上加入了Schema(数据结构信息)
4.DataFrame Schema支持嵌套数据类型:struct,map.array
5.提供更多类似SQL操作的API
创建DataFrame
/** 将JSON文件转成DataFrame
* people.json内容如下
* {"name":"Michael"}
* {"name":"Andy", "age":30}
* {"name":"Justin", "age":19}
*/
val df = spark.read.json("file:///home/hadoop/data/people.json")
// 使用show方法将DataFrame的内容输出
df.show
- DataFrame API常用操作
val df = spark.read.json("file:///home/hadoop/data/people.json")
// 使用printSchema方法输出DataFrame的Schema信息
df.printSchema()
// 使用select方法来选择我们所需要的字段
df.select("name").show()
// 使用select方法选择我们所需要的字段,并未age字段加1
df.select(df("name"), df("age") + 1).show()
// 使用filter方法完成条件过滤
df.filter(df("age") > 21).show()
// 使用groupBy方法进行分组,求分组后的总数
df.groupBy("age").count().show()
//sql()方法执行SQL查询操作
df.createOrReplaceTempView("people")
spark.sql("SELECT * FROM people").show
- RDD->DF
object Demo6 extends App {
val spark = SparkSession.builder().master("local[*]").appName("aa").getOrCreate()
val sc = spark.sparkContext
import spark.implicits._
private val textRDD: RDD[Array[String]] = sc.textFile("D:\\IDEA\\_20200806_sparktest\\data\\people.txt").map(_.split(","))
//todo 自定义一个schema信息
//zhangsan,29
private val schema = StructType(Array(
StructField("name", StringType, true),
StructField("age", IntegerType, true)
))
//todo 把rdd转换成Row
private val makeRDD: RDD[Row] = textRDD.map(x=>Row(x(0),x(1).trim.toInt))
//todo 把RDD转换成DataFrame
private val df1: DataFrame = spark.createDataFrame(makeRDD,schema)
df1.printSchema()
df1.show()
private val rddres: RDD[Row] = df1.rdd
println(rddres.collect().mkString(" "))
- DataFrame ->RDD
/** people.json内容如下
* {"name":"Michael"}
* {"name":"Andy", "age":30}
* {"name":"Justin", "age":19}
*/
val df = spark.read.json("file:///home/hadoop/data/people.json")
//将DF转为RDD
df.rdd.collect