Spark学习总结(二)—— Spark Sql
1、SparkSQL
SparkSQL是spark用于结构化数据处理的spark模块
Hive 是早期唯一运行在 Hadoop 上的 SQL-on-Hadoop 工具。
但是 MapReduce 计算过程中大量的中间磁盘落地过程消耗了大量的 I/O,降低的运行效率,为了提高 SQL- on-Hadoop的效率,大量的 SQL-on-Hadoop 工具开始产生
其中 Shark 是伯克利实验室 Spark 生态环境的组件之一,是基于 Hive 所开发的工具,它修
改了下图所示的右下角的内存管理、物理计划、执行三个模块,并使之能运行在 Spark 引擎上。
但是,随着 Spark 的发展,对于野心勃勃的 Spark 团队来说,Shark 对于 Hive 的太多依
赖(如采用 Hive 的语法解析器、查询优化器等等),制约了 Spark 的 One Stack Rule Them All的既定方针, 制约了 Spark 各个组件的相互集成,所以提出了 SparkSQL 项目
SparkSQL 主要提供了2个编程抽象,类似于RDD,它们分别为DataFrame和DataSet
2、特点
- 易整合
无缝的整合了SQL查询和Spark编程
- 统一的数据访问
使用相同的方式连接不同的数据源
- 兼容Hive
在已有的仓库上直接运行SQL或者HiveQL
- 标准数据连接
通过JDBC或者ODBC来连接
3、DataFrame
DataFrame 是一种以 RDD 为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame 与 RDD 的主要区别在于,前者带有 schema 元信息,即 DataFrame所表示的二维表数据集的每一列都带有名称和类型。
DataFrame 是为数据提供了 Schema 的视图。可以把它当做数据库中的一张表来对待
DataFrame 也是懒执行的,但性能上比 RDD 要高,主要原因:优化的执行计划,即查询计划通过 Spark catalyst optimiser 进行优化。
4、DataSet
DataSet 是分布式数据集合。DataSet 是 Spark 1.6 中添加的一个新抽象,是 DataFrame的一个扩展。它提供了 RDD 的优势(强类型,使用强大的 lambda 函数的能力)以及 Spark SQL 优化执行引擎的优点。
DataSet 也可以使用功能性的转换(操作 map,flatMap,filter等等)。
它可以把表中的一行当做一个对象来操作,就可以避免查询时的属性顺序问题
5、DataFrame核心编程
在 Spark SQL 中 SparkSession 是创建 DataFrame 和执行 SQL 的入口,创建 DataFrame
有三种方式:
(1)通过 Spark 的数据源进行创建;
如果从内存中获取数据,spark 可以知道数据类型具体是什么。如果是数字,默认作为 Int 处理;但是从文件中读取的数字,不能确定是什么类型,所以用 bigint 接收,可以和Long 类型转换,但是和 Int 不能进行转换
scala> val df = spark.read.json("data/user.json") df: org.apache.spark.sql.DataFrame = [age: bigint, username: string]
(2)从一个存在的 RDD 进行转换;
SQL 语法风格是指我们查询数据的时候使用 SQL 语句来查询,这种风格的查询必须要有临时视图或者全局视图来辅助
scala> df.createOrReplaceTempView("people") scala> val sqlDF = spark.sql("SELECT * FROM people") sqlDF: org.apache.spark.sql.DataFrame = [age: bigint, name: string]
注意,普通临时表是 Session 范围内的,如果想应用范围内有效,可以使用全局临时表。使用全局临时表时需要全路径访问,例如global_temp.people
(3)从 Hive Table 进行查询返回;
注意:涉及到运算的时候, 每列都必须使用$, 或者采用引号表达式:单引号+字段名
scala> df.select($"username",$"age" + 1).show scala> df.select('username, 'age + 1).show()
6、RDD之间的转换
将RDD中的数据转换成DataFrame模式
//List(1,2,3,4) -> table(id : 1,2,3,4) scala> idRDD.toDF("id").show //table(id : 1,2,3,4) -> List(1,2,3,4) val rdd = df.rdd
注意:此时得到的 RDD 存储类型为 Row
7、DataSet核心编程
DataSet 是具有强类型的数据集合,需要提供对应的类型信息。它包含了DataFrame中的属性以及RDD中的数据,相当于将DataFrame封装成了一个类
8、DataFrame和DataSet转换
//DataFrame 转换 DataSet //构造类 scala> case class User(name:String, age:Int) defined class User scala> val df = sc.makeRDD(List(("zhangsan",30), ("lisi",49))).toDF("name","age") df: org.apache.spark.sql.DataFrame = [name: string, age: int] scala> val ds = df.as[User] ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int] //DataSet 转 换 DataFrame scala> val ds = df.as[User] ds: org.apache.spark.sql.Dataset[User] = [name: string, age: int] scala> val df = ds.toDF df: org.apache.spark.sql.DataFrame = [name: string, age: int]
9、RDD和DataSet转换
//RDD 转换 DataSet scala> case class User(name:String, age:Int) defined class User scala> sc.makeRDD(List(("zhangsan",30), ("lisi",49))).map(t=>User(t._1, t._2)).toDS res11: org.apache.spark.sql.Dataset[User] = [name: string, age: int]
注意:转换的前提是预先将RDD中的数据用转换类(User)进行封装,这样转换后才能使用方便
//DataSet 转 换 RDD scala> val rdd = res11.rdd rdd: org.apache.spark.rdd.RDD[User] = MapPartitionsRDD[51] at rdd at <console>:25 scala> rdd.collect res12: Array[User] = Array(User(zhangsan,30), User(lisi,49))
10、RDD & DataSet & DataFrame三者关系
三者的共性
(1)RDD、DataFrame、DataSet 全都是 spark 平台下的分布式弹性数据集,为处理超大型数据提供便利;
(2)三者都有惰性机制,在进行创建、转换,如 map 方法时,不会立即执行,只有在遇到Action 如
foreach 时,三者才会开始遍历运算;
(3)三者有许多共同的函数,如 filter,排序等;
(4)在对 DataFrame 和 Dataset 进行操作许多操作都需要这个包:import spark.implicits._(在创建好
SparkSession 对象后尽量直接导入)
(5)三者都会根据 Spark 的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
(6)三者都有 partition 的概念
(7)DataFrame 和 DataSet 均可使用模式匹配获取各个字段的值和类型
三者的区别
(1)RDD
【1】RDD 一般和 spark mllib 同时使用
【2】RDD 不支持 sparksql 操作
(2)DataFrame
【1】与 RDD 和 Dataset 不同,DataFrame 每一行的类型固定为 Row,每一列的值没法直接访问,只有通过解析才能获取各个字段的值
【2】DataFrame 与 DataSet 一般不与 spark mllib 同时使用
【3】DataFrame 与 DataSet 均支持 SparkSQL 的操作,比如 select,groupby 之类,还能注册临时表/视窗,进行 sql 语句操作
【4】DataFrame 与 DataSet 支持一些特别方便的保存方式,比如保存成 csv,可以带上表头,这样每一列的字段名一目了然(后面专门讲解)
(3)DataSet
【1】Dataset 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同。DataFrame 其实就是 DataSet 的一个特例 type DataFrame = Dataset[Row]
【2】DataFrame 也可以叫 Dataset[Row],每一行的类型是 Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的 getAS 方法或者共性中的第七条提到的模式匹配拿出特定字段。而 Dataset 中,每一行是什么类型是不一定的,在自定义了 case class 之后可以很自由的获得每一行的信息
11、用户自定义函数
(1)UDF
用户可以通过spark.udf功能添加自定义的函数来实现自定义的功能
【1】创建DataFrame
scala> val df = spark.read.json("data/user.json")
【2】注册UDF
scala> spark.udf.register("addName",(x:String)=> "Name:"+x)
【3】创建临时表
scala> df.createOrReplaceTempView("people")
【4】应用UDF
scala> spark.sql("Select addName(name),age from people").show()
(2)UDAF
强类型的 Dataset 和弱类型的 DataFrame 都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),
max(),min()。除此之外,用户可以设定自己的自定义聚合函数。通过继承UserDefinedAggregateFunction 来重写实现用户自定义弱类型聚合函数。从 Spark3.0 版本后,UserDefinedAggregateFunction 已经不推荐使用了。可以统一采用强类型聚合函数Aggregator
12、数据的加载和保存
(1)通用的加载和保存方式
SparkSQL 提供了通用的保存数据和数据加载的方式。这里的通用指的是使用相同的API,根据不同的参数读取和保存不同格式的数据,SparkSQL 默认读取和保存的文件格式为 parquet
【1】加载数据:spark.read.load
- format("…"):指定加载的数据类型,括"csv"、"jdbc"、"json"、"orc"、"parquet"和
"textFile"。
- load("…"):在"csv"、"jdbc"、"json"、"orc"、"parquet"和"textFile"格式下需要传入加载数据的路径。
- option("…"):在"jdbc"格式下需要传入 JDBC 相应参数,url、user、password 和 dbtable
【2】保存数据:df.write.save
- format("…"):指定保存的数据类型,包括"csv"、"jdbc"、"json"、"orc"、"parquet"和"textFile"。
- save ("…"):在"csv"、"orc"、"parquet"和"textFile"格式下需要传入保存数据的路径。
- option("…"):在"jdbc"格式下需要传入 JDBC 相应参数,url、user、password 和 dbtable
保存操作可以使用 SaveMode, 用来指明如何处理数据,使用 mode()方法来设置。有一点很重要: 这些 SaveMode 都是没有加锁的, 也不是原子操作。
(2) Parquet
Spark SQL 的默认数据源为 Parquet 格式。Parquet 是一种能够有效存储嵌套数据的列式存储格式。
数据源为 Parquet 文件时,Spark SQL 可以方便的执行所有的操作,不需要使用 format。修改配置项spark.sql.sources.default,可修改默认数据源格式。
【1】加载数据
scala> val df = spark.read.load("examples/src/main/resources/users.parquet")
【2】保存数据
var df = spark.read.json("/opt/module/data/input/people.json") //保存为 parquet 格式 scala> df.write.mode("append").save("/opt/module/data/output")