文章目录
SparkSQL
一、SparkSQL的发展
1.1、概述
SparkSQL,顾名思义,就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身叫Shark,最开始的底层代码优化,sql的解析,执行引擎等等完全基于HIve,Shark的执行速度要比Hive高出一个数量级,但是Hive的发展制约了Shark,所以在15年中旬的时候,Shark项目结束,重新独立出来一个项目,就是SparkSQL,不再依赖Hive,做了独立的发展,逐渐的形成两条互相独立的业务:Sparksql,和Hive-OnSpark。在SparkSQL发展的过程中,同时也吸收了Shark有些特点:基于内存的列存储,动态字节码优化技术
1.2、特点
1.3、总结
SparkSQL就是Spark生态体系中用于处理结构化数据的一个模块。结构化数据是什么?存储在关系型数据库中的数据,就是结构化数据;半结构化数据是什么?类似xml、json等的格式的数据被称之为半结构化数据;非结构化数据是什么?音频、视频、图片等为非结构化数据。
换句话说,SparkSQL处理的就是二维表数据。
二、SparkSQL的编程模型(即代码风格:DSL和SQL)
2.1、编程模型简介
主要通过两种方式操作SparkSQL,一种就是SQL,另一种为DataFrame和Dataset。
- SQL
SQL不用多说,就和Hive操作一样,但是需要清楚一点的时候,SQL操作的是表,所以要想用SQL进行操作,就需要将SparkSQL对应的编程模型转化成为一张表才可以。
同时支持,通用sql和hiveql。
- DataFrame和Dataset
DataFrame和Dataset是SparkSQL中的编程模型。DataFrame和Dataset我们都可以理解为是一张mysql中的二维表,表有什么?表头,表名,字段,字段类型。RDD其实说白了也是一张二维表,但是这张二维表相比较于DataFrame和Dataset却少了很多东西,比如表头,表名,字段,字段类型,只有数据。
Dataset是在spark1.6.2开始出现出现的api,DataFrame是1.3的时候出现的,早期的时候DataFrame叫SchemaRDD,SchemaRDD和SparkCore中的RDD相比较,就多了Schema,所谓约束信息,元数据信息。
一般的,将RDD称之为Spark体系中的第一代编程模型;DataFrame比RDD多了一个Schema元数据信息,被称之为Spark体系中的第二代编程模型;Dataset吸收了RDD的优点(强类型推断和强大的函数式编程)和DataFrame中的优化(SQL优化引擎,内存列存储),成为Spark的最新一代的编程模型。
2.2、RDD、DataFrame、DataSet的对比
2.2.1、RDD
弹性分布式数据集。可分区,不可变(只读),内部元素可以并行计算。是Spark对数据进行的一种抽象,RDD就是一种数据结构,里面包含了数据和操作数据的方法(可以片面的将RDD理解为集合)
RDD的弹性:
1、可以自动切换内存和磁盘(当内存不够时,存入磁盘)
2、当某一个RDD的数据丢失时,可以通过血缘关系追溯到上一个RDD重新计算,不用从头计算
3、当某一个任务或阶段执行失败后,会自动进行重试,默认4次
4、RDD中的数据是分区的,分区的大小可以自由设置和细粒度调整
RDD的分布式:
RDD的数据可以存放在多个节点上
数据集:
就是一个存放数据的集合
相对于与DataFrame和Dataset,RDD是Spark最底层的抽象,目前是开发者用的最多的,但逐步会转向DataFrame和Dataset(当然,这是Spark的发展趋势)
2.2.2、DataFrame
DataFrame:理解了RDD,DataFrame就容易理解些,DataFrame的思想来源于Python的pandas库,RDD是一个数据集,DataFrame在RDD的基础上加了Schema(描述数据的信息,可以认为是元数据,DataFrame曾经就有个名字叫SchemaRDD),下面有图片很形象的表现出来了两者的差别:
假设RDD中的两行数据长这样:
那么DataFrame中的数据长这样:
从上面两个图可以看出,DataFrame比RDD多了一个表头信息(Schema),像一张表了,DataFrame还配套了新的操作数据的方法:DataFrame API,下面会说到。
有了DataFrame这个高一层的抽象后,我们处理数据更加简单了,甚至可以用SQL来处理数据了,对开发者来说,易用性有了很大的提升。不仅如此,通过DataFrame API或SQL处理数据,会自动经过Spark 优化器(Catalyst)的优化,即使你写的程序或SQL不高效,也可以运行的很快!
2.2.3、DataSet
相对于RDD,Dataset提供了强类型支持,也是在RDD的每行数据加了类型约束
如下图:
假设RDD中的两行数据长这样:
那么Dataset中的数据长这样:
或者长这样(每行数据是个Object):
使用Dataset API的程序,会经过Spark SQL的 优化器(Catalyst) 进行优化。
目前仅支持Scala、Java API,尚未提供Python的API,相比DataFrame,Dataset还提供了编译时类型检查,对于分布式程序来讲,提交一次作业太费劲了(要编译、打包、上传、运行),到提交到集群运行时才发现错误,很烦,这也是引入Dataset的一个重要原因。
2.2.4、RDD、DataFrame、DataSet的对比
1、RDD和DataFrame的比较
RDD描述的是数据结构,以及提供了操作方式,并且具有弹性特点,也可以理解为RDD是一张二维表
DataFrame以前叫SchemaRDD,从名字上就可以知道,比RDD多了一个Schema,而schema就是元数据
元数据有表名,表头,字段,字段类型
DataFrame易用性更好,底层可以自动优化。即使你写的sql比较复杂,运行速度也非常快
2、RDD和DataSet的比较
相同点:DataSet也引入了RDD的强类型推断,也是在RDD的每行数据加了类型约束
不同点:DataSet还可以映射成java对象
运行时,DataSet也会自动优化
3、DataFrame与DataSet的比较
相同点:都有schema
不同点:DataFrame 没有 编译时检查机制
DataSet 有 编译时检查机制
三、SparkSQL的编程入口
在SparkSQL中的编程模型,不在是SparkContext,但是创建需要依赖SparkContext。SparkSQL中的编程模型,在spark2.0以前的版本中为SQLContext和HiveContext,HiveContext是SQLContext的一个子类,提供Hive中特有的一些功能,比如row_number开窗函数等等,这是SQLContext所不具备的,在Spark2.0之后将这两个进行了合并——SparkSession。
SparkSession的构建需要依赖SparkConf或者SparkContext。
使用工厂构建器(Builder方式)模式创建SparkSession。
四、IDEA中编写SparkSQL的入口
4.1、在IDEA中创建SparkSQL模块
只要引入SparkSQL相关的依赖即可,如下:
<properties>
<scala.version>2.11.8</scala.version>
<spark.version>2.2.3</spark.version>
</properties>
<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.11</artifactId>
<version>${spark.version}</version>
</dependency>
</dependencies>
4.2、下面是创建SparkSQL入口的语法:SQLContext,HiveContext,SparkSession
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.{DataFrame, SQLContext, SparkSession}
import org.apache.spark.{SparkConf, SparkContext}
/**
* sparksql的程序入口方法
*/
object AppAccessDemo {
def main(args: Array[String]): Unit = {
//spark2.0以前,有两个,分别是sqlContext和hiveContext
val conf: SparkConf = new SparkConf().setMaster("local").setAppName("test")
val sc = new SparkContext(conf)
//第一种:获取一个sqlContext
val sqlContext = new SQLContext(sc)
val df: DataFrame = sqlContext.read.json("data/a.json")
df.show()
//第二种:获取一个hiveContext
val hiveContext = new HiveContext(sc)
val frame: DataFrame = hiveContext.table("")
frame.show()
//第三种:使用spark2.0以后的SparkSession对象
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
val df: DataFrame = spark.read.json("data/a.json")
df.show()
sc.stop()
}
}
五、SparkSQL的基本编程
5.1、SparkSession的创建方式
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession}
object _02SparkSessionCreateMethod {
def main(args: Array[String]): Unit = {
//第一种:
val spark: SparkSession =SparkSession.builder().appName("test").master("local").getOrCreate()
val df: DataFrame = spark.read.json("data/a.json")
df.show()
//第二种
val conf: SparkConf = new SparkConf().setAppName("test").setMaster("local")
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
val df: DataFrame = spark.read.json("data/a.json")
df.show()
//第三种
val spark: SparkSession = SparkSession.builder().appName("test")
.master("local")
.enableHiveSupport() //开启访问hive的支持
.getOrCreate()
spark.table("").show()
}
}
5.2、基本编程
package com.xxx.SparkSQL.Day01
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.{Column, DataFrame, SparkSession}
object _03SparkSqlFirst {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().appName("test").master("local").getOrCreate()
//读取一个json文件,获得一个DataFrame对象,数据格式如下:
// {"movie":"1193","rate":"5","datetime":"978300760","uid":"1"}
val df: DataFrame = spark.read.json("data/a.json")
//show():是一个行动算子,用于将结果打印到控制台,相当于sql的 select * from tableName
//df.show() //默认显示前20行
//df.show(30,true) 指定要显示的行数,和字段的值的长度如果超出了30个字符是否要截断显示 就是一个
//cell中的内容大于30个字符只显示30个字符,并且右对齐 ,false表示全部显示,左对齐
df.show()
//printSchema():此方法将显示二维表的结构(元数据:表明,表头,字段名,字段类型)
df.printSchema()
//也可以这样写,比较繁琐,不常用
val schema: StructType = df.schema
schema.printTreeString()
//具体的一些查询
df.select("movie","rate","uid").show()
//导入SparkSession中的隐式转换操作,增强SQL的功能.注意:spark不是包名,而是sparkSeesion对象的名称
import spark.implicits._
//这时就可以使用变量了,变量可以用.的方式来调用方法
df.select(($"movie").+(10),$"rate" +10,$"datetime").show()
//还有一种字段的写法 使用new Column
df.select(new Column("movie"),new Column("uid")).show()
//起别名
df.select($"movie" as("电影"),$"rate".as("评分"),$"datetime".as("时间")).show()
//做聚合,统计
df.select($"movie" as("电影")).groupBy("movie").count().show()
//条件查询
df.select($"movie" as("电影"),$"rate".as("评分"),$"datetime".as("时间"))
.where("rate > 4 and datetime < 956354156")
.show()
/*
上面的写法都是使用DataFrame或者DataSet,下面写SQL的写法
要用SQL的写法必须事先将对应的数据映射为一张表才行,有四种映射方法
df.createOrReplaceGlobalTempView(): global是全局的意思,表示整个spark程序中都可以访问到
df.createGlobalTempView() 没有global:仅当前任务中可以访问的
df.createOrReplaceTempView() replace: 有replace,表示如果存在就会替换,不存在也创建
df.createTempView() 无replace, 如果存在就报错,不存在就创建
注意:建议使用 createOrReplaceTempView 或 createTempView
*/
df.registerTempTable("movie") //在spark2.0之后就抛弃了换用下面的方法
df.createOrReplaceTempView("movie")
//使用SQL语法进行查询
spark.sql(
"""
|select
|*
|from movie
|where rate > 4
|""".stripMargin).show()
//也可以提前定义SQL语句
val sql = "select * from movie where rate > 4"
spark.sql(sql).show()
//关闭SparkSession(用完关闭,养成好习惯)
spark.stop()
}
}
5.3、SparkSQL编程模型的操作
5.3.1、DataFrame的构建
构建方式有三种:
1、就是上面使用的用read()方法读取文件,返回一个DataFrame
2、通过javaBean+反射来构建
3、通过动态编码的方式来构建
package com.xxx.SparkSQL.Day01
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.types.{DataTypes, StructField, StructType}
import org.apache.spark.sql.{DataFrame, Row, SparkSession}
object _04CreateDataFrameMethod {
def main(args: Array[String]): Unit = {
javaBean
dynamicProgramme
}
def javaBean: Unit = {
val spark: SparkSession = SparkSession.builder().master("local").appName("create").getOrCreate()
import spark.implicits._
//描述一个java的学生集合对象,要使用java中对应的class
val students = List(new Student(1001, "zs", "f", 23),
new Student(1002, "ls", "m", 23),
new Student(1003, "ww", "f", 25),
new Student(1004, "zl", "f", 26))
//调用createDataFrame方法需要导包
import scala.collection.JavaConversions._
//调用SparkSession的createDataFrame方法
val df: DataFrame = spark.createDataFrame(students, classOf[Student])
df.show()
spark.stop()
}
def dynamicProgramme: Unit = {
val spark: SparkSession = SparkSession.builder().master("local").appName("create").getOrCreate()
import spark.implicits._
//获取一个RDD对象,要使用SparkContext,SparkSession对象里可以直接获取
//Row:行,就是代表了二维表中的一行记录
val rdd1: RDD[Row] = spark.sparkContext.parallelize(List(
Row(1001, "zs", "m", 23),
Row(1002, "ls", "m", 24),
Row(1003, "ww", "m", 25),
Row(1004, "zl", "m", 26),
Row(1005, "xq", "m", 27)
))
//获取RDD的描述信息(RDD数据的元数据)
val schema: StructType = StructType(List(
StructField("id", DataTypes.IntegerType, false), //false代表不可为空,true可为空
StructField("name", DataTypes.StringType, false),
StructField("gender", DataTypes.StringType, false),
StructField("age", DataTypes.IntegerType, false)
))
val df: DataFrame = spark.createDataFrame(rdd1, schema)
df.show()
spark.stop()
}
}
说明,这里学习三个新的类:
Row:代表的是二维表中的一行记录,或者就是一个Java对象
StructType:是该二维表的元数据信息,是StructField的集合
StructField:是该二维表中某一个字段/列的元数据信息(主要包括,列名,类型,是否可以为null)
总结:
这两种方式,都是非常常用,但是动态编程更加的灵活,因为javabean的方式的话,提前要确定好数据格式类型,后期无法做改动。
5.3.2、DataSet的构建
DataSet就是DataFrame的升级版,创建方式和DataFrame类似,但有所不同
package com.xxx.SparkSQL.Day01
import org.apache.spark.sql.{Dataset, SparkSession}
/**
* DataSet就是DataFrame的升级版
*
* spark.createDataset(List|Seq|Array)
* 创建方式是一样的,都可以使用动态编程方式
* 1. 参数是scala的集合对象
* 2. 有一个隐式参数:需要导入spark的隐式方法
* 3. 集合的元素类型是一个样例类
*/
object _05CreateDataSetMethod {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().master("local").appName("create").getOrCreate()
import spark.implicits._
//维护一个普通集合
val girls = List(
Girls(1001,"zs","m",23),
Girls(1002,"ls","m",24),
Girls(1003,"ww","m",25),
Girls(1004,"zl","m",26)
)
val ds: Dataset[Girls] = spark.createDataset(girls)
ds.show()
spark.stop()
}
}
case class Girls(id: Int, name: String, gender: String, age: Int)
在创建Dataset的时候,需要注意数据的格式,必须使用case class,或者基本数据类型,同时需要通过import spark.implicts._来完成数据类型的编码,而抽取出对应的元数据信息,否则编译无法通过
5.3.3、RDD和DataFrame以及DataSet之间的相互转换
1、RDD转DataFrame,DataSet
import spark.implicits._
rdd.toDF()
rdd.toDS()
2、DataFrame,DataSet转RDD
df.rdd
ds.rdd
3、DataFrame转DataSet
DataFrame无法直接转DataSet,但是可以调用算子实现转换(orderBy,groupBy等)
val ds: Dataset[Row] = df.orderBy($"deptno".desc)
4、DatSet转DataFrame
ds.toDF