一、sparkcore的复习
一. spark的简介
1. spark是scala语言编写的一个计算框架
2. spark是一个快速的,通用的,运行在分布式上的一个大数据集的计算分析框架
3. 快速的原因就是因为spark处理的数据是基于内存存储的(与MR相比的非常重要的区别)
4. spark的组件包括:
sparkcore(提供了RDD的通用编程模型),
sparksql(交互式编程),
sparkStreaming(流式处理),
GraphX(图计算),
MLlib(机器学习)
5. 与MR的区别:
- 存储位置: spark是内存, MR是基于磁盘
- 容错性(失败时):
spark通过血缘关系找到上一个RDD重新计算即可,无需从头开始
MR的job某一部分失败后,会从头开始
- 通用性:spark提供了两大类的RDD的API,以及流处理,图计算等变成模型
MR只提供了Map阶段和Reduce阶段的简单编程模型
- 框架复杂度:由于spark的编程模型以及内部组件众多,所以复杂
MR只提供了Map阶段和Reduce阶段的简单编程模型
MR也不是没有优点:和spark比较起来,生态简单,运行稳定,适合后台长期运行
二. spark的部署(作业运行位置,调度工具)
-. 部署方式:
local: 只有master(或者说是本地操作系统)
standalone:主从架构(一个master,多个worker)
yarn: hadoop的一个资源管理器
mesos:apache的一个调度工具
k8s: 企业中常用的一款性能非常好的调度工具
- standalone模式的搭建
三. spark作业提交:
1. 使用spark-submit指令提交作业,以及各种参数等
--master
--class
--executor-cores
--executor-memory
.....
注意:如果不指定executor-cores,默认总数是2个
2. standalone的提交方法:也有两种client,cluster
spark-submit
--class classFullName
--master url
--deploy-mode client|cluster
jarPath 100
3. yarn方法的提交:也有两种client,cluster
spark-submit
--class classFullName
--master yarn
--deploy-mode client|cluster
jarPath 100
四. spark-shell
1. 是spark提供了一个shell交互界面,可以进行简单的编程
2. 还提供了webui访问端口
3. 还提供了SparkContext的变量
4. 也有local和cluster模式
cluster模式使用的是standalone集群
五. RDD的简介
1. 弹性分布式数据集(resilient distributed dataset),是数据的抽象概念,也可以理解为是数据的引用
2. 三大特征:不可变的(只读),可分区的,并行计算
3. 结构:
abstract class RDD{
partitions:分区列表
sparkContext: 上下文
sparkConf: 配置对象
compute: 计算每一个分区的函数
dependencies:依赖关系
partitioner:分区器
preferred location: 要分析的数据的优先位置
checkpiont:是否进行了检查点设置
store level:存储级别
iterator: 用于从内存中读取真正的数据
}
4.RDD的弹性特点:
-存储的弹性
-容错的弹性
-重试次数的弹性
-重新分片
5. RDD的分类:
PairRDD 指的都是KV形式的RDD
六. RDD的编程
1. 其实就是算子的使用
2. 算子分类:两大类
- 转换transformation算子
功能:将一个RDD转成一个新的RDD
特点:分窄依赖算子和宽依赖算子
窄依赖:上游RDD的一个分区对应下游RDD的一个分区
宽依赖:上游RDD的一个分区对应下游RDD的多个分区,里面都是有shuffle机制
stage的划分一定是宽依赖造成(划分)的
但是宽依赖算子不一定会划分stage
rdd1.sortByKey().groupByKey().groupByKey()
- 行动Action算子
触发一个job
将RDD的数据发送给Driver或者是搜集后输出
七. spark深入了解
1. 依赖关系:
宽和窄依赖
2. DAG: 有开始节点和结束节点,但是所有的节点没有闭环情况。就是一个有向无环图
DAG的结束节点一定是一个action算子,正好是一个job的划分
血统关系:在出错时,方便追溯上游的RDD
3. job、stage、taskSet的划分
stage的划分:是一个宽依赖算子
taskSet:每一个stage中都会多个Task. task的个数由分区数决定
4. cache、persist、checkpoint
cache(): 缓存到内存 缓存的数据都会自动删除 官方建议使用
persist(....):可以指定缓存等级 缓存的数据都会自动删除 官方建议使用
checkPoint: 支持化到磁盘上,需要指定路径。持久化的数据需要手动删除
5. 广播变量:
减少Driver向Task发送的数据的网络IO,向executor发送一份
6. 累加器:
driver里的变量想要获取每一个executor里局部信息的汇总,需要搜集。
而累加器就可以实现局部的累加以及向driver端汇报
7. 自定义排序:
- 简单排序
- 要排序的类型实现Ordered特质
- 使用隐式参数进行排序: Ordering类型的对象
8. 分区器:
HashPartitioner
RangePartitioner
自定义:需要实现Partitioner特质
9. 文件的输入输出
- 纯文本文件: textFile saveAsTextFile
- json :textFile saveAsTextFile
- csv :textFile saveAsTextFile
- sequence: sc.sequence saveAsSequenceFile
10. JDBCRDD:可以连接数据库,进行范围查询操作
二、sparksql的简介
2.1 简介
1. sparksql是spark中的一个重要组件
2. 构建在SparkCore基础之上的一个基于SQL的计算模块。
3. 前身叫shark,基于hive,hive的发展制约了shark的发展
4. 后来shark项目停止了,独立出来一个新的,叫sparksql,带有shark的优点:基于内存的列式存储,动态字节码优化技术
5. 用于处理具有结构化的大数据集
2.2 特点
1. 集成:
可以在spark项目上使用sql,可以使用java、scala、python、r语言
2. 统一的数据源访问:
使用通用的方法访问各种数据源:avro,parquet,orc,json,jdbc等
3. 集成了sql和hivesql
支持普通的sql以及hivesql在hive warehouse上运行。
4. 标准的连接方式
可以使用jdbc或者是odbc连接sparksql
三、sparksql的编程模型和入口
3.1 两种编程模型(也可以说成风格)
3.1.1 SQL编程模型(SQL风格)
就是使用我们学过的通用的sql(包括标准sql以及hivesql)语句进行执行数据的查询和计算
注意:如果想要使用这种写法,那么sparksql中必须要将对应的数据维护成一个表。
3.1.2 DataFrame和DataSet编程模型(DSL风格)
1. DataFrame 是spark1.3以后出现的, DataSet是spark1.6以后出现的
2. DataFrame和DataSet都是对真正要处理的数据的一个抽象,和RDD概念相似。
3. DataFrame和DataSet比RDD多了一个schema描述。
4. schema指的就是表名,表头,字段,字段类型等描述信息
称呼:
RDD编程称之为spark第一代编程模型
DataFrame比RDD多了一个Schema元数据信息,被称之为Spark体系中的第二代编程模型
Dataset吸收了RDD的优点(强类型推断和强大的函数式编程)和DataFrame中的优化(SQL优化引擎,内存列存储),成为Spark的最新一代的编程模型。
3.2 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有编译器检查机制
3.3 编程入口
1. 在sparkSql中,不再使用SparkConext,但是也会依赖于SparkContext。
2. 在Spark2.0以前,具有两个入口,分别是
- SqlContext,就是提供了一些算子及其普通操作
- HiveContext:是SqlContext的子类,提供了一些特殊的方法,比如开窗函数等
3. 在Spark2.0以后,将两者进行合并,成了一个SparkSession,SparkSession的构建会使用工厂方法。
pom依赖
<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>
package com.qf.sql.day01
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的基本编程
4.1 SparkSession的三种创建方式
package com.qf.sql.day01
import org.apache.spark.SparkConf
import org.apache.spark.sql.{DataFrame, SparkSession}
object _02SparkSessionCreateMethod {
def main(args: Array[String]): Unit = {
//第一种方法: 直接使用SparkSession的builder构建器,如果想要设置配置信息,可以调用.config(String,String),多参数的时候可以连续调用
/* val spark: SparkSession = SparkSession.builder()
.appName("test")
// .config("master","local")
// .config("","")
// .config("","")
.master("local")
.getOrCreate()
val df: DataFrame = spark.read.json("data/a.json")
df.show()*/
//第二种方法:就是使用SparkConf绑定配置信息
/* val conf = new SparkConf().setMaster("local").setAppName("test").set("a","true")
val spark: SparkSession = SparkSession.builder().config(conf).getOrCreate()
spark.read.json("data/a.json").show()*/
//第三种方法:获取连接hive的SparkSession对象
val spark: SparkSession = SparkSession.builder().master("local")
.appName("test")
.enableHiveSupport() //开启访问hive的支持
.getOrCreate()
spark.table("").show()
spark.stop()
}
}
4.2 SparkSql的常用方法
4.2.1 几个常用的算子
package com.qf.sql.day01
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.{Column, DataFrame, SparkSession}
object _03SparkSqlFirst {
def main(args: Array[String]): Unit = {
//获取SparkSession对象
val spark = SparkSession.builder().appName("test").master("local").getOrCreate()
import spark.implicits._
//读取一个json文件,返回DataFrame对象,可以当成一个二维表
val df: DataFrame = spark.read.json("data/emp.json")
/**
* show(): 是一个行动算子,用于将结果打印到控制台上
* 无参数方法:表示列出Top20条
*
* show(numrows:Int) :可以指定要显示的行数
* show(numRows:Int,truncate:boolean) :第二个参数表示字段的值如果超出了20个字符,是否要截断显示
* true:表示截断,右对齐
* false:表示全显示,左对齐
*
* 相当于:select * from ....
*/
// df.show(10,false)
//显示schema: schema是元数据,即表名,表头,字段名,字段类型
// val schema: StructType = df.schema
// schema.printTreeString()
//简单写法,打印schema
// df.printSchema()
//select算子: 可以指定字段名称
// val df1: DataFrame = df.select("empno", "ename", "sal", "deptno")
// val df1: DataFrame = df.select(new Column("empno"),new Column("ename"),new Column("job"))
//如果想要使用$,需要导入SparkSession的隐式转换, 建议,不管是否使用隐式转换,也应该导入
// import spark.implicits._
// val df1: DataFrame = df.select($"empno",$"ename",$"job")
// df1.show()
//where算子: 用于指定条件的
/* val df2: DataFrame = df.select("empno", "ename", "sal", "deptno")
.where("deptno<30")
df2.show()
val df3: DataFrame = df.select("empno", "ename", "sal", "deptno")
.where("deptno<30 and sal<3000")
df3.show()
val df4: DataFrame = df.select("empno", "ename", "sal", "deptno")
.where("deptno<30")
.where("sal<3000")
df4.show()*/
//字段可以进行计算
/* val df5: DataFrame = df.select($"empno", $"ename",$"sal", $"sal"+1000, $"deptno")
val df5: DataFrame = df.select($"empno", $"ename",$"sal", $"sal".+(1000), $"deptno")
df5.show()*/
//取别名
// df.select($"empno", $"ename",$"sal".as("salary"), ($"sal"+1000).as("涨薪后"), $"deptno").show()
//分组算子:groupBy,
// 求总数: count
df.groupBy("deptno").count().show()
spark.stop()
}
}
4.2.2 其他Action操作
show
以表格的形式在输出中展示DataFrame中的数据,该方法有四种调用方式,分别为:
(1)show
只显示前20条记录。且过长的字符串会被截取
scala> employeeDF.show
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female| Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
| 42| 3| male| Tom| 18000.0|
| 21| 3|female| Kattie| 21000.0|
| 19| 2| male| Jen| 8000.0|
| 30| 2|female| Jen| 28000.0|
| 42| 3| male| Tom| 18000.0|
| 18| 3|female|XiaoFang| 58000.0|
+---+-----+------+--------+---------+
(2)show(numRows: Int)
显示numRows条
scala> employeeDF.show(3)
+---+-----+------+-----+---------+
|age|depId|gender| name| salary|
+---+-----+------+-----+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female|Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
+---+-----+------+-----+---------+
only showing top 3 rows
(3)show(truncate: Boolean)
是否截取20个字符,默认为true。
scala> employeeDF.show(true)
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female| Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
| 42| 3| male| Tom| 18000.0|
| 21| 3|female| Kattie| 21000.0|
| 19| 2| male| Jen| 8000.0|
| 30| 2|female| Jen| 28000.0|
| 42| 3| male| Tom| 18000.0|
| 18| 3|female|XiaoFang| 58000.0|
+---+-----+------+--------+---------+
(4)show(numRows: Int, truncate: Int)
显示记录条数,以及截取字符个数,为0时表示不截取
scala> employeeDF.show(3, 2)
+---+-----+------+----+------+
|age|depId|gender|name|salary|
+---+-----+------+----+------+
| 25| 1| ma| Le| 20|
| 30| 2| fe| Ma| 25|
| 35| 1| ma| Ja| 15|
+---+-----+------+----+------+
only showing top 3 rows
collect
获取DataFrame中的所有数据并返回一个Array对象
scala> employeeDF.collect
res6: Array[org.apache.spark.sql.Row] = Array([25,1,male,Leo,20000.126], [30,2,female,Marry,25000.0], [35,1,male,Jack,15000.0], [42,3,male,Tom,18000.0], [21,3,female,Kattie,21000.0], [19,2,male,Jen,8000.0], [30,2,female,Jen,28000.0], [42,3,male,Tom,18000.0], [18,3,female,XiaoFang,58000.0])
collectAsList
功能和collect类似,只不过将返回结构变成了List对象
scala> employeeDF.collectAsList
res7: java.util.List[org.apache.spark.sql.Row] = [[25,1,male,Leo,20000.126], [30,2,female,Marry,25000.0], [35,1,male,Jack,15000.0], [42,3,male,Tom,18000.0], [21,3,female,Kattie,21000.0], [19,2,male,Jen,8000.0], [30,2,female,Jen,28000.0], [42,3,male,Tom,18000.0], [18,3,female,XiaoFang,58000.0]]
describe
这个方法可以动态的传入一个或多个String类型的字段名,结果仍然为DataFrame对象,用于统计数值类型字段的统计值,比如count, mean, stddev, min, max等。
scala> employeeDF.describe("age", "salary").show()
+-------+-----------------+------------------+
|summary| age| salary|
+-------+-----------------+------------------+
| count| 9| 9|
| mean|29.11111111111111|23444.458444444444|
| stddev|9.198429817697752|14160.779261027332|
| min| 18| 8000.0|
| max| 42| 58000.0|
+-------+-----------------+------------------+
first, head, take, takeAsList
(1)first获取第一行记录
(2)head获取第一行记录,head(n: Int)获取前n行记录
(3)take(n: Int)获取前n行数据
(4)takeAsList(n: Int)获取前n行数据,并以List的形式展现
以Row或者Array[Row]的形式返回一行或多行数据。first和head功能相同。
take和takeAsList方法会将获得到的数据返回到Driver端,所以,使用这两个方法时需要注意数据量,以免Driver发生OutOfMemoryError。
以上方法简单,使用过程和结果略。
4.2.3 条件查询和Join操作
以下返回为DataFrame类型的方法,可以连续调用。
where条件
等同于SQL语言中where关键字。传入筛选条件表达式,可以用and和or。得到DataFrame类型的返回结果。
scala> employeeDF.where("depId = 3").show
+---+-----+------+--------+-------+
|age|depId|gender| name| salary|
+---+-----+------+--------+-------+
| 42| 3| male| Tom|18000.0|
| 21| 3|female| Kattie|21000.0|
| 42| 3| male| Tom|18000.0|
| 18| 3|female|XiaoFang|58000.0|
+---+-----+------+--------+-------+
scala> employeeDF.where("depId = 3 and salary > 18000").show
+---+-----+------+--------+-------+
|age|depId|gender| name| salary|
+---+-----+------+--------+-------+
| 21| 3|female| Kattie|21000.0|
| 18| 3|female|XiaoFang|58000.0|
+---+-----+------+--------+-------+
filter过滤
传入筛选条件表达式,得到DataFrame类型的返回结果。和where使用条件相同。
scala> employeeDF.filter("depId = 3 or salary > 18000").show
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female| Marry| 25000.0|
| 42| 3| male| Tom| 18000.0|
| 21| 3|female| Kattie| 21000.0|
| 30| 2|female| Jen| 28000.0|
| 42| 3| male| Tom| 18000.0|
| 18| 3|female|XiaoFang| 58000.0|
+---+-----+------+--------+---------+
查询指定字段
select
根据传入的String类型字段名,获取指定字段的值,以DataFrame类型返回。
scala> employeeDF.select("name", "age").show(3)
+-----+---+
| name|age|
+-----+---+
| Leo| 25|
|Marry| 30|
| Jack| 35|
+-----+---+
only showing top 3 rows
还有一个重载的select方法,不是传入String类型参数,而是传入Column类型参数。可以实现select id, id+1 from table
这种逻辑。
scala> employeeDF.select(employeeDF("age"), employeeDF("age") + 1 ).show(5)
+---+---------+
|age|(age + 1)|
+---+---------+
| 25| 26|
| 30| 31|
| 35| 36|
| 42| 43|
| 21| 22|
+---+---------+
only showing top 5 rows
能得到Column类型的方法是column和col方法,col方法更简便一些。
scala> employeeDF.select(col("age"), col("age") + 1 ).show(5)
+---+---------+
|age|(age + 1)|
+---+---------+
| 25| 26|
| 30| 31|
| 35| 36|
| 42| 43|
| 21| 22|
+---+---------+
only showing top 5 rows
selectExpr
可以直接对指定字段调用UDF函数,或者指定别名等。传入String类型参数,得到DataFrame对象。
scala> employeeDF.selectExpr("name" , "depId as departmentId" , "round(salary)").show()
+--------+------------+----------------+
| name|departmentId|round(salary, 0)|
+--------+------------+----------------+
| Leo| 1| 20000.0|
| Marry| 2| 25000.0|
| Jack| 1| 15000.0|
| Tom| 3| 18000.0|
| Kattie| 3| 21000.0|
| Jen| 2| 8000.0|
| Jen| 2| 28000.0|
| Tom| 3| 18000.0|
|XiaoFang| 3| 58000.0|
+--------+------------+----------------+
col
只能获取一个字段,返回对象为Column类型。
scala> employeeDF.select(col("name")).show(3)
+-----+
| name|
+-----+
| Leo|
|Marry|
| Jack|
+-----+
only showing top 3 rows
drop
返回一个新的DataFrame对象,其中不包含去除的字段,一次只能去除一个字段。
scala> employeeDF.drop("gender").show(3)
+---+-----+-----+---------+
|age|depId| name| salary|
+---+-----+-----+---------+
| 25| 1| Leo|20000.126|
| 30| 2|Marry| 25000.0|
| 35| 1| Jack| 15000.0|
+---+-----+-----+---------+
only showing top 3 rows
limit
limit方法获取指定DataFrame的前n行记录,得到一个新的DataFrame对象。和take与head不同的是,limit方法不是Action操作。
scala> employeeDF.limit(3).show
+---+-----+------+-----+---------+
|age|depId|gender| name| salary|
+---+-----+------+-----+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female|Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
+---+-----+------+-----+---------+
order by
orderBy和sort
按指定字段排序,默认为升序。加个“-”号表示降序排序。只对数字类型和日期类型生效。sort和orderBy使用方法相同。
scala> employeeDF.orderBy(- col("salary")).show(3)
scala> employeeDF.sort(- col("salary")).show(3)
+---+-----+------+--------+-------+
|age|depId|gender| name| salary|
+---+-----+------+--------+-------+
| 18| 3|female|XiaoFang|58000.0|
| 30| 2|female| Jen|28000.0|
| 30| 2|female| Marry|25000.0|
+---+-----+------+--------+-------+
only showing top 3 rows
sortWithinPartitions
和上面的sort方法功能类似,区别在于sortWithinPartitions方法返回的是按Partition排好序的DataFrame对象。
val repartitioned = employeeDF.repartition(2)
repartitioned.sortWithinPartitions("salary").show
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 35| 1| male| Jack| 15000.0|
| 25| 1| male| Leo|20000.126|
| 21| 3|female| Kattie| 21000.0|
| 30| 2|female| Jen| 28000.0|
| 18| 3|female|XiaoFang| 58000.0|
| 19| 2| male| Jen| 8000.0|
| 42| 3| male| Tom| 18000.0|
| 42| 3| male| Tom| 18000.0|
| 30| 2|female| Marry| 25000.0|
+---+-----+------+--------+---------+
group by
groupBy
根据指定字段进行group by操作,可以指定多个字段。
groupBy方法有两种调用方式,可以传入String类型的字段名,也可传入Column类型的对象。
employeeDF.groupBy("depId").count.show
employeeDF.groupBy(employeeDF("depId")).count.show
+-----+-----+
|depId|count|
+-----+-----+
| 1| 2|
| 3| 4|
| 2| 3|
+-----+-----+
cube和rollup
功能类似于SQL中的group by cube/rollup。
cube:为指定表达式集的每个可能组合创建分组集。首先会对(A、B、C)进行group by,然后依次是(A、B),(A、C),(A),(B、C),(B),( C),最后对全表进行group by操作。
rollup:在指定表达式的每个层次级别创建分组集。group by A,B,C with rollup首先会对(A、B、C)进行group by,然后对(A、B)进行group by,然后是(A)进行group by,最后对全表进行group by操作。
employeeDF.cube("depId","gender").sum("salary").show()
+-----+------+------------------+
|depId|gender| sum(salary)|
+-----+------+------------------+
| 3|female| 79000.0|
| 1| male|35000.126000000004|
| 1| null|35000.126000000004|
| null| null| 211000.126|
| 3| null| 115000.0|
| 2| male| 8000.0|
| 3| male| 36000.0|
| null|female| 132000.0|
| 2| null| 61000.0|
| null| male| 79000.126|
| 2|female| 53000.0|
+-----+------+------------------+
employeeDF.rollup("depId","gender").sum("salary").show()
+-----+------+------------------+
|depId|gender| sum(salary)|
+-----+------+------------------+
| 3|female| 79000.0|
| 1| male|35000.126000000004|
| 1| null|35000.126000000004|
| null| null| 211000.126|
| 3| null| 115000.0|
| 2| male| 8000.0|
| 3| male| 36000.0|
| 2| null| 61000.0|
| 2|female| 53000.0|
+-----+------+------------------+
GroupedData对象
该方法得到的是GroupedData类型对象,在GroupedData的API中提供了group by之后的操作,比如:
-
max(colNames: String*),获取分组中指定字段或者所有的数字类型字段的最大值,只能作用于数字类型字段
-
min(colNames: String*),获取分组中指定字段或者所有的数字类型字段的最小值,只能作用于数字类型字段
-
mean(colNames: String*),获取分组中指定字段或者所有的数字类型字段的平均值,只能作用于数字类型字段
-
sum(colNames: String*),获取分组中指定字段或者所有的数字类型字段的和值,只能作用于数字类型字段
-
count(),获取分组中的元素个数
运行结果示例:
employeeDF.groupBy("depId").count.show +-----+-----+ |depId|count| +-----+-----+ | 1| 2| | 3| 4| | 2| 3| +-----+-----+ employeeDF.groupBy("depId").max("salary").show +-----+-----------+ |depId|max(salary)| +-----+-----------+ | 1| 20000.126| | 3| 58000.0| | 2| 28000.0| +-----+-----------+
distinct
distinct
返回当前DataFrame中不重复的Row记录。
employeeDF.distinct
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 35| 1| male| Jack| 15000.0|
| 30| 2|female| Jen| 28000.0|
| 19| 2| male| Jen| 8000.0|
| 18| 3|female|XiaoFang| 58000.0|
| 25| 1| male| Leo|20000.126|
| 42| 3| male| Tom| 18000.0|
| 30| 2|female| Marry| 25000.0|
| 21| 3|female| Kattie| 21000.0|
+---+-----+------+--------+---------+
dropDuplicates
根据指定字段去重。类似于select distinct a, b操作。
employeeDF.dropDuplicates(Seq("name")).show
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 35| 1| male| Jack| 15000.0|
| 42| 3| male| Tom| 18000.0|
| 19| 2| male| Jen| 8000.0|
| 30| 2|female| Marry| 25000.0|
| 21| 3|female| Kattie| 21000.0|
| 25| 1| male| Leo|20000.126|
| 18| 3|female|XiaoFang| 58000.0|
+---+-----+------+--------+---------+
聚合
聚合操作调用的是agg方法,该方法有多种调用方式。一般与groupBy方法配合使用。
以下示例其中最简单直观的一种用法,对age字段求最大值,对salary字段求和。
employeeDF.agg("age" -> "max", "salary" -> "sum").show
+--------+-----------+
|max(age)|sum(salary)|
+--------+-----------+
| 42| 211000.126|
+--------+-----------+
union
对两个DataFrame进行合并
employeeDF.union(employeeDF.limit(1)).show
+---+-----+------+--------+---------+
|age|depId|gender| name| salary|
+---+-----+------+--------+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female| Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
| 42| 3| male| Tom| 18000.0|
| 21| 3|female| Kattie| 21000.0|
| 19| 2| male| Jen| 8000.0|
| 30| 2|female| Jen| 28000.0|
| 42| 3| male| Tom| 18000.0|
| 18| 3|female|XiaoFang| 58000.0|
| 25| 1| male| Leo|20000.126|
+---+-----+------+--------+---------+
join
在SQL语言中很多场景需要用join操作,DataFrame中同样也提供了join的功能。 并且提供了六个重载的join方法。
using一个字段形式
下面这种join类似于a join b using column1的形式,需要两个DataFrame中有相同的一个列名。
val employeeDF2 = spark.read.json("hdfs://node01:9000/company/employee.json")
employeeDF.join(employeeDF2, "depId").show
+-----+---+------+------+---------+---+------+--------+---------+
|depId|age|gender| name| salary|age|gender| name| salary|
+-----+---+------+------+---------+---+------+--------+---------+
| 1| 25| male| Leo|20000.126| 35| male| Jack| 15000.0|
| 1| 25| male| Leo|20000.126| 25| male| Leo|20000.126|
| 2| 30|female| Marry| 25000.0| 30|female| Jen| 28000.0|
| 2| 30|female| Marry| 25000.0| 19| male| Jen| 8000.0|
| 2| 30|female| Marry| 25000.0| 30|female| Marry| 25000.0|
| 1| 35| male| Jack| 15000.0| 35| male| Jack| 15000.0|
| 1| 35| male| Jack| 15000.0| 25| male| Leo|20000.126|
| 3| 42| male| Tom| 18000.0| 18|female|XiaoFang| 58000.0|
| 3| 42| male| Tom| 18000.0| 42| male| Tom| 18000.0|
| 3| 42| male| Tom| 18000.0| 21|female| Kattie| 21000.0|
| 3| 42| male| Tom| 18000.0| 42| male| Tom| 18000.0|
| 3| 21|female|Kattie| 21000.0| 18|female|XiaoFang| 58000.0|
| 3| 21|female|Kattie| 21000.0| 42| male| Tom| 18000.0|
| 3| 21|female|Kattie| 21000.0| 21|female| Kattie| 21000.0|
| 3| 21|female|Kattie| 21000.0| 42| male| Tom| 18000.0|
| 2| 19| male| Jen| 8000.0| 30|female| Jen| 28000.0|
| 2| 19| male| Jen| 8000.0| 19| male| Jen| 8000.0|
| 2| 19| male| Jen| 8000.0| 30|female| Marry| 25000.0|
| 2| 30|female| Jen| 28000.0| 30|female| Jen| 28000.0|
| 2| 30|female| Jen| 28000.0| 19| male| Jen| 8000.0|
+-----+---+------+------+---------+---+------+--------+---------+
using多个字段形式
除了上面这种using一个字段的情况外,还可以using多个字段,如下:
employeeDF.join(employeeDF2, Seq("name", "depId")).show
+--------+-----+---+------+---------+---+------+---------+
| name|depId|age|gender| salary|age|gender| salary|
+--------+-----+---+------+---------+---+------+---------+
| Leo| 1| 25| male|20000.126| 25| male|20000.126|
| Marry| 2| 30|female| 25000.0| 30|female| 25000.0|
| Jack| 1| 35| male| 15000.0| 35| male| 15000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Kattie| 3| 21|female| 21000.0| 21|female| 21000.0|
| Jen| 2| 19| male| 8000.0| 30|female| 28000.0|
| Jen| 2| 19| male| 8000.0| 19| male| 8000.0|
| Jen| 2| 30|female| 28000.0| 30|female| 28000.0|
| Jen| 2| 30|female| 28000.0| 19| male| 8000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
|XiaoFang| 3| 18|female| 58000.0| 18|female| 58000.0|
+--------+-----+---+------+---------+---+------+---------+
指定join类型
在上面的using多个字段的join情况下,可以写第三个String类型参数,指定join的类型,如下所示:
inner:内连
outer,full,full_outer:全连
left, left_outer:左连
right,right_outer:右连
left_semi:过滤出joinDF1中和joinDF2共有的部分
left_anti:过滤出joinDF1中joinDF2没有的部分
employeeDF.join(employeeDF2, Seq("name", "depId"), "inner").show
+--------+-----+---+------+---------+---+------+---------+
| name|depId|age|gender| salary|age|gender| salary|
+--------+-----+---+------+---------+---+------+---------+
| Leo| 1| 25| male|20000.126| 25| male|20000.126|
| Marry| 2| 30|female| 25000.0| 30|female| 25000.0|
| Jack| 1| 35| male| 15000.0| 35| male| 15000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Kattie| 3| 21|female| 21000.0| 21|female| 21000.0|
| Jen| 2| 19| male| 8000.0| 30|female| 28000.0|
| Jen| 2| 19| male| 8000.0| 19| male| 8000.0|
| Jen| 2| 30|female| 28000.0| 30|female| 28000.0|
| Jen| 2| 30|female| 28000.0| 19| male| 8000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
| Tom| 3| 42| male| 18000.0| 42| male| 18000.0|
|XiaoFang| 3| 18|female| 58000.0| 18|female| 58000.0|
+--------+-----+---+------+---------+---+------+---------+
使用Column类型来join
如果不用using模式,灵活指定join字段的话,可以使用如下形式:
employeeDF.join(departmentDF, employeeDF("depId") === departmentDF("id")).show
+---+-----+------+--------+---------+---+--------------------+
|age|depId|gender| name| salary| id| name|
+---+-----+------+--------+---------+---+--------------------+
| 25| 1| male| Leo|20000.126| 1|Technical Department|
| 30| 2|female| Marry| 25000.0| 2|Financial Department|
| 35| 1| male| Jack| 15000.0| 1|Technical Department|
| 42| 3| male| Tom| 18000.0| 3| HR Department|
| 21| 3|female| Kattie| 21000.0| 3| HR Department|
| 19| 2| male| Jen| 8000.0| 2|Financial Department|
| 30| 2|female| Jen| 28000.0| 2|Financial Department|
| 42| 3| male| Tom| 18000.0| 3| HR Department|
| 18| 3|female|XiaoFang| 58000.0| 3| HR Department|
+---+-----+------+--------+---------+---+--------------------+
在指定join字段同时指定join类型
employeeDF.join(departmentDF, employeeDF("depId") === departmentDF("id"), "inner").show
+---+-----+------+--------+---------+---+--------------------+
|age|depId|gender| name| salary| id| name|
+---+-----+------+--------+---------+---+--------------------+
| 25| 1| male| Leo|20000.126| 1|Technical Department|
| 30| 2|female| Marry| 25000.0| 2|Financial Department|
| 35| 1| male| Jack| 15000.0| 1|Technical Department|
| 42| 3| male| Tom| 18000.0| 3| HR Department|
| 21| 3|female| Kattie| 21000.0| 3| HR Department|
| 19| 2| male| Jen| 8000.0| 2|Financial Department|
| 30| 2|female| Jen| 28000.0| 2|Financial Department|
| 42| 3| male| Tom| 18000.0| 3| HR Department|
| 18| 3|female|XiaoFang| 58000.0| 3| HR Department|
+---+-----+------+--------+---------+---+--------------------+
获取指定字段统计信息
stat方法可以用于计算指定字段或指定字段之间的统计信息,比如方差,协方差等。这个方法返回一个DataFramesStatFunctions类型对象。
下面代码演示根据字段,统计该字段值出现频率在30%以上的内容。在employeeDF中字段depId的内容为"1,2,3"。其中"1,2,3"出现频率均大于0.3。
employeeDF.stat.freqItems(Seq ("depId") , 0.3).show()
+---------------+
|depId_freqItems|
+---------------+
| [2, 1, 3]|
+---------------+
获取两个DataFrame中共有的记录
intersect方法可以计算出两个DataFrame中相同的记录。
employeeDF.intersect(employeeDF.limit(1)).show()
+---+-----+------+----+---------+
|age|depId|gender|name|salary |
+---+-----+------+----+---------+
|25 |1 |male |Leo |20000.126|
+---+-----+------+----+---------+
获取一个DataFrame中有另一个DataFrame中没有的记录
employeeDF.except(employeeDF.limit(1)).show()
+---+-----+------+--------+-------+
|age|depId|gender|name |salary |
+---+-----+------+--------+-------+
|42 |3 |male |Tom |18000.0|
|21 |3 |female|Kattie |21000.0|
|19 |2 |male |Jen |8000.0 |
|30 |2 |female|Jen |28000.0|
|35 |1 |male |Jack |15000.0|
|30 |2 |female|Marry |25000.0|
|18 |3 |female|XiaoFang|58000.0|
+---+-----+------+--------+-------+
操作字段名
withColumnRenamed
如果指定的字段名不存在,不进行任何操作。下面示例中将employeeDF中的depId字段重命名为departmentId。
employeeDF.withColumnRenamed("depId", "departmentId").show
+---+------------+------+--------+---------+
|age|departmentId|gender| name| salary|
+---+------------+------+--------+---------+
| 25| 1| male| Leo|20000.126|
| 30| 2|female| Marry| 25000.0|
| 35| 1| male| Jack| 15000.0|
| 42| 3| male| Tom| 18000.0|
| 21| 3|female| Kattie| 21000.0|
| 19| 2| male| Jen| 8000.0|
| 30| 2|female| Jen| 28000.0|
| 42| 3| male| Tom| 18000.0|
| 18| 3|female|XiaoFang| 58000.0|
+---+------------+------+--------+---------+
withColumn
whtiColumn(colName: String , col: Column)方法根据指定colName往DataFrame中新增一列,如果colName已存在,则会覆盖当前列。
employeeDF.withColumn("salary2", employeeDF("salary")).show
+---+-----+------+--------+---------+---------+
|age|depId|gender| name| salary| salary2|
+---+-----+------+--------+---------+---------+
| 25| 1| male| Leo|20000.126|20000.126|
| 30| 2|female| Marry| 25000.0| 25000.0|
| 35| 1| male| Jack| 15000.0| 15000.0|
| 42| 3| male| Tom| 18000.0| 18000.0|
| 21| 3|female| Kattie| 21000.0| 21000.0|
| 19| 2| male| Jen| 8000.0| 8000.0|
| 30| 2|female| Jen| 28000.0| 28000.0|
| 42| 3| male| Tom| 18000.0| 18000.0|
| 18| 3|female|XiaoFang| 58000.0| 58000.0|
+---+-----+------+--------+---------+---------+
行转列
有时候需要根据某个字段内容进行分割,然后生成多行,这时可以使用explode方法 。
下面代码中,根据name字段中的空格将字段内容进行分割,分割的内容存储在新的字段name_中,如下所示:
departmentDF.explode("name", "name_"){x:String => x.split(" ")}.show
+---+--------------------+----------+
| id| name| name_|
+---+--------------------+----------+
| 1|Technical Department| Technical|
| 1|Technical Department|Department|
| 2|Financial Department| Financial|
| 2|Financial Department|Department|
| 3| HR Department| HR|
| 3| HR Department|Department|
+---+--------------------+----------+
4.3 两种风格写法的说明
package com.qf.sql.day01
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession}
object _04SparkSqlExercise {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local").getOrCreate()
import spark.implicits._
/**
* DSL风格的写法:
* Domain-specific language :特殊领域的语言
*/
val df: DataFrame = spark.read.json("data/emp.json")
val value: Dataset[Row] = df.select($"empno", $"ename",$"deptno")
.where("sal<3000 and deptno <30")
.orderBy($"deptno".desc)
value.show()
/**
* SQL风格的写法,说的就是SQl编程模型
*
* 注意:需要为读取到的数据维护一张表
*
* df.createOrReplaceGlobalTempView(): global是全局的意思,表示整个spark程序中都可以访问到
* df.createGlobalTempView() 没有global:仅当前任务中可以访问的
* df.createOrReplaceTempView() replace: 有replace,表示如果存在就会替换,不存在也创建
* df.createTempView() 无replace, 如果存在就报错,不存在就创建
*
*
* 注意:建议使用 createOrReplaceTempView 或 createTempView
*
*/
df.createOrReplaceTempView("emp")
val sql = "select deptno, count(*),max(sal),min(comm) from emp group by deptno"
val frame: DataFrame = spark.sql(sql.stripMargin)
frame.show()
spark.stop()
}
}
4.4 DataFrame和Dataset的创建方式
4.4.1 DataFrame的创建方式
1. 可以直接使用SparkSession.read获取DataFrameReader阅读器对象,然后调用相应的读取文件的方法来返回一
个Dataframe对象
2. 使用javaBean+反射机制,获取DataFrame对象,
需要调用createDataFrame(data: java.util.List[_], beanClass: Class[_])
3. 使用动态编程方式:
也就是要使用Row,StructType,StrunctField三种类型来描述,同时使用
createDataFrame(rowRDD: RDD[Row], schema: StructType)
4. 从rdd转换而来:
需要隐式导入:import spark.implicits._
rdd.toDF()
rdd.toDF(colNames: String*) :用于指定rdd转成df时的列名
案例:
package com.qf.sql.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 _05DataFrameCreatemethod {
/**
* 方法3:使用动态编程
*
* Row:代表的是二维表中的一行记录,相当于一个Java对象
* StructType:是该二维表的元数据信息,是StructField的集合
* StructField:是该二维表中某一个字段/列的元数据信息(主要包括,列名,类型,是否可以为null)
* @param args
*/
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local").getOrCreate()
//获取一个RDD对象,要使用SparkContext. sparkSession对象里可以直接获取
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数据的元数据
val schema = StructType(List(
StructField("id",DataTypes.IntegerType,false),
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()
}
/**
* 方法1:就是使用DataFrameReader来读取文件
* 方法2:使用javaBean+反射机制,获取DataFrame对象
* @param args
*/
def javaBean(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local").getOrCreate()
import spark.implicits._
//描述一个学生集合对象
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))
import scala.collection.JavaConversions._
//调用SparkSession的createDataFrame方法
val df: DataFrame = spark.createDataFrame(students, classOf[Student])
df.show()
spark.stop()
}
}
4.4.2 Dataset的创建方式
package com.qf.sql.day01
import org.apache.spark.sql.{Dataset, SparkSession}
/**
* DataSet就是DataFrame的升级版
*
*
* spark.createDataset(List|Seq|Array)
* 创建方式是一样的,都可以使用动态编程方式
* 1. 参数是scala的集合对象
* 2. 有一个隐式参数:需要导入spark的隐式方法
* 3. 集合的元素类型是一个样例类
*/
object _06DataSetCreatemethod {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("test").master("local").getOrCreate()
//维护一个普通集合
val girls =List(
Girl(1001, "zs", "m", 23),
Girl(1002, "ls", "m", 24),
Girl(1003, "ww", "m", 25),
Girl(1004, "zl", "m", 26),
Girl(1005, "xq", "m", 27)
)
import spark.implicits._
val ds: Dataset[Girl] = spark.createDataset(girls)
ds.show()
spark.stop()
}
}
case class Girl(id:Int,name:String,gender:String,age:Int)
4.5 RDD与DataFrame、Dataset之间的转换
4.5.1 RDD的转换
RDD–>DataFrame: 可以调用createDataFrame()
RDD->Dataset:可以调用createDataset()
--也可以直接调用相应方法转
比如:toDF(),toDS(), 需要隐式导入
4.5.2 DataFrame的转换
DataFrame->RDD : df.rdd
DataFrame->Dataset :不能直接转,需要调用其他算子
4.5.3 Dataset的转换
Dataset->RDD : ds.rdd
Dataset->DataFrame : ds.toDF()
五、sparksql的加载与落地
5.1 SparkSql的加载
1. 正规方法应该使用
spark.read.load(path:String)
2. load方法默认加载的是parquet文件
3. 如果想要加载其他格式的文件,需要使用format(source:String)
比如:
spark.read.format("json").load(path:String)
spark.read.format("csv").load(path:String)
//想要获取csv的第一行作为表头
spark.read.format("csv").option("header","true").load(path:String)
4. 可以读取数据库中的数据,可以使用jdbc方法
spark.read.jdbc(url: String, table: String, properties: Properties)
package com.qf.sql.day02
import java.util.Properties
import org.apache.spark.sql.{DataFrame, SparkSession}
object _01SqlFileLoad {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().appName("load").master("local").getOrCreate()
//读取文件的方式:
/**
* 正规加载文件的方式是load方法, load方法默认加载的是parquet存储格式的文件
*
* 如果想要读取其他格式的文件,需要使用format方法来指定具体格式
*/
/*val df: DataFrame = spark.read.load("data/users.parquet")*/
/* val df: DataFrame = spark.read.format("json").load("data/emp.json")*/
/**
* 加载csv文件时,默认使用的是逗号作为分隔符进行切分成多个字段的
*/
/*val df: DataFrame = spark.read.format("csv").load("data/country.csv")*/
// 这种读取不是我们想要的结果
/*val df: DataFrame = spark.read.format("csv").load("data/ip-pprovince-count.csv")*/
//需要指定分隔符进行切分. option("sep","指定分隔符")
// 如果想要第一行作为表头需要使用option("header","true")
/* val df: DataFrame = spark.read.format("csv").option("sep",";").option("header","true").load("data/ip-pprovince-count.csv")
df.select("counts").filter("province=='北京'").show()*/
/*val df: DataFrame = spark.read.format("orc").load("data/student.orc")*/
// 纯文本文件,只能解析成一列,option("sep",",")是无效的
/* val df: DataFrame = spark.read.format("text").option("sep",",").load("data/people.txt")
df.show*/
/*
* 我们可以使用简化的方式来读取各种类型的数据源
* spark.read.json()
* spark.read.csv()
* spark.read.load()
* spark.read.orc()
* spark.read.text()
*/
/*
* 练习读取mysql中的数据
*/
val prop = new Properties()
prop.setProperty("user","root")
prop.setProperty("password","123456")
val df: DataFrame = spark.read.jdbc(
"jdbc:mysql://localhost:3306/hz2003", "emp", prop)
df.show()
spark.stop()
}
}
5.2 SparkSql的落地
1. 使用的方法是
df.write.save(path:String)
2. 默认写出的存储格式是parquet
3. 如果想要使用其他格式作为存储,需要使用format(source:String)
如果是csv格式的存储,可以将表头写到第一行
df.write.format("csv").option("header","true").save(path:String)
4. 如果想要保存到数据库中
df.write.jdbc(url: String, table: String, properties: Properties)
注意存储模式:
SaveMode.Append :从后追加,注意主键问题
SaveMode.Overwrite :删除并重建
SaveMode.ErrorIfExists :如果存在就抛异常 默认模式
SaveMode.Ignore :如果数据存在,就不进行保存DataFrame的数据
如果想要指定格式:
df.write.mode(SaveMode.Append )
.jdbc(url: String, table: String, properties: Properties)
六、SparkSql与hive的整合
SparkSQL和Hive的整合,是一种比较常见的关联处理方式,SparkSQL加载Hive中的数据进行业务处理,同时将计算结果落地回Hive中。
在这里,我们的目的是用SparkSql来读取Hive里的数据,而不是更换hive的执行引擎.
drop table emp;
create table emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int,
deptno int
)
row format delimited
fields terminated by ",";
select deptno,
max(sal),
min(sal),
round(avg(nvl(sal,0)),0)
from
emp
group by deptno
order by deptno desc
load data local inpath "/root/emp.txt" into table emp;
drop table emp1;
create table emp2(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal int,
comm int,
deptno int
)
partitioned by(dt string)
row format delimited
fields terminated by ","
注意事项:
在整合过程中,可能会遇到以下问题:
1. 权限问题
方法1: hdfs dfs -chmod -R 777 /
方法2: 在代码中使用System.setProperty("HADOOP_USER_NAME","root"),这一行要放在获取SparkSession对象之前
2. 落地到hive时,只有表结构,没有表数据。
原因:idea在使用resources目录下的hive-site.xml这个文件时,没有将路径解析成HDFS上,而是解析成了本地的路径,将数据写在了本地,不是hdfs.
解决办法:配置以下属性
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property>
七、SparkSql的函数
7.1 说明
1. Sparksql中的函数多数都是指其他编程语言中的函数,比如hive中的函数,在SparkSql中都可以使用。
2. 当然SparkSql也提供了自己的内置函数,如果想要使用内置函数,需要导入内置函数
import org.apache.spark.sql.Functions._
3. 用户也可以自定义函数,比如UDF(一对一),UDAF(多对一,比如max),UDTF(一对多,比如explode)函数
4. 函数的分类
- 从功能上分类: 日期函数,数值,字符串函数,统计函数,特殊函数
- 从自定义函数上(实现方式)分类:UDF,UDAF,UDTF。
7.2 hive函数的回顾
1. 查看内置函数,及其帮助信息
hive> show functions;
hive> desc function funcName
2. unix_timestamp() : 可以获取当前系统的时间戳,单位是秒, 返回的是本初子午线的时间戳
hive (sz2003)> select unix_timestamp("1970-01-01 00:00:00");
OK
3. 将时间戳转成日期:from_unixtime(unix_time, format) 注意:输入的是本初子午线的时间戳,返回的是东八区的日期
hive> select from_unixtime(0,"yyyy-MM-dd HH:mm:ss");
4. to_date() :将年月份时分秒的格式截取年月日 ,截取时非常好用
5. 六个时间分量函数:其实就是用于截取日期时间的六个部分
year()
month()
day()
hour()
minute()
second()
6.
select instr("helloworld","h");
instr(string,substring) :返回的是substring在string中的第一次遇到的第一个字符的位置。
开窗函数的:
package com.qf.sql.day03
import org.apache.spark.sql.{DataFrame, SparkSession}
object _01FunctionDemo01 {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().appName("test").master("local").getOrCreate()
val df: DataFrame = spark.read.json("data/emp.json")
import spark.implicits._
import org.apache.spark.sql.functions._
//获取入职日期的分量
df.select(year(to_date($"hiredate","yyyy-MM-dd")).as("year"),
month(to_date($"hiredate","yyyy-MM-dd")).as("month"),
dayofmonth(to_date($"hiredate","yyyy-MM-dd")).as("day")).show()
/**
* 创建临时表
*/
df.createTempView("emp")
spark.sql(
"""
|select distinct deptno,
|count(1) over(partition by deptno order by deptno)
|from emp
|""".stripMargin).show()
spark.stop()
}
}
7.3 用户自定义函数
1)UDF案例1
package com.qf.sql.day02
import org.apache.spark.sql.{DataFrame, SparkSession}
/**
* 用户自定义函数的应用
* 1. 定义一个函数:
* 2. 注册函数:
* spark.udf.register(name:String,func:Function)
*/
object _05UserDefineFunction {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().master("local").appName("test").getOrCreate()
val df: DataFrame = spark.read.json("file:///D:\\Users\\Michael\\Documents\\IdeaProjects\\sz2003\\sz2003_sparksql01/data/emp.json")
/**
* 换SQL风格
*/
//维护一个临时表
df.createTempView("emp")
//定义一个函数:
val getLength=(str:String)=>str.length
//注册函数
spark.udf.register("mylength",getLength)
val sql=
"""
|select ename,
|mylength(ename)
|from
|emp
|""".stripMargin
spark.sql(sql).show()
spark.stop()
}
}
2)UDF案例2
package com.qf.sql.day02
import org.apache.spark.sql.{DataFrame, SparkSession}
object _06UserDefineFunction {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().master("local").appName("test").getOrCreate()
val df: DataFrame = spark.read.json("file:///D:\\Users\\Michael\\Documents\\IdeaProjects\\sz2003\\sz2003_sparksql01/data/emp.json")
//维护一张临时奥b
df.createTempView("emp")
//注册函数
spark.udf.register("getLevel",getLevel _)
/* val sql =
"""
|select ename,
|job,
|sal,
|case when sal>3000 then 'level1'
|when sal>1500 then 'level2'
|else 'level3' end as level
|from
|emp
|""".stripMargin*/
val sql =
"""
|select ename,
|job,
|sal,
|getLevel(sal) as level
|from
|emp
|""".stripMargin
spark.sql(sql).show()
spark.stop()
}
def getLevel(sal:Int)={
if(sal>3000){
"level1"
}else if(sal>1500){
"level2"
}else{
"level3"
}
}
}
3)UDAF案例演示
package com.qf.sql.day02
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.expressions.{MutableAggregationBuffer, UserDefinedAggregateFunction}
import org.apache.spark.sql.types.{DataType, DataTypes, StructField, StructType}
import org.apache.spark.sql.{Dataset, Row, SparkSession}
/**
* 用户自定义分析函数UDAF案例演示:
* 自定义一个求平均值的函数: 多对一。
* @param id
* @param name
* @param score
*/
case class Score(id:Int,name:String,score:Double)
object Test04 {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder().appName("udaf").master("local").getOrCreate()
val list = List(
Score(1,"张三",99.9),
Score(2,"李四",88.9),
Score(3,"小明",77.9),
Score(1,"张三",91.9),
Score(2,"李四",81.9),
Score(3,"小明",71.9)
)
import spark.implicits._
//scala的对象可以直接转DS或者是DF, 当然需要导入隐式转换
val ds: Dataset[Score] = list.toDS()
/*维护一张表*/
ds.createTempView("tmp")
//注册函数:
spark.udf.register("myavg",new MyAvgUDAF)
val sql = "select avg(score), myavg(score),name from tmp group by name"
spark.sql(sql).show()
}
}
/**
* 1:需要继承 UserDefinedAggregateFunction
* 2:重写方法
*/
class MyAvgUDAF extends UserDefinedAggregateFunction{
/**
* 指定用户自定义udaf输入参数的元数据
* @return
*/
override def inputSchema: StructType = {
StructType(Array(StructField("score",DataTypes.DoubleType)))
}
/**
* udaf自定义函数求解过程中的临时变量的数据类型
*
* 因为要求平均值,正常逻辑是求出总和以及个数,然后做除法运算,因此要有两个临时变量
* @return
*/
override def bufferSchema: StructType = {
StructType(Array(
StructField("sum",DataTypes.DoubleType),
StructField("count",DataTypes.IntegerType)
))
}
/**
* udaf返回值的数据类型
* @return
*/
override def dataType: DataType = DataTypes.DoubleType
override def deterministic: Boolean = true
/**
* 临时变量是存储在buffer中,我们定义的buffer有两个元素,第一元素是sum,第二个元素是count
* @param buffer
*/
override def initialize(buffer: MutableAggregationBuffer): Unit = {
buffer.update(0,0d) //设置第一个元素的的初始值为0d
buffer.update(1,0) // 设置第二个元素的初始值为0
}
/**
* 分区内的局部累加操作
* @param buffer
* @param input
*/
override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
val score: Double = input.getAs[Double](0)
buffer.update(0,buffer.getDouble(0)+score) //将新进来的行中的分数累加到第一个元素sum上
buffer.update(1,buffer.getInt(1)+1) //将第二个元素count累加一个1
}
/**
* 分区间的累加操作
* @param buffer1
* @param buffer2
*/
override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
buffer1.update(0,buffer1.getDouble(0)+buffer2.getDouble(0))
buffer1.update(1,buffer1.getInt(1)+buffer2.getInt(1))
}
/**
* 用于计算输出结果
* @param buffer
* @return
*/
override def evaluate(buffer: Row): Any = buffer.getDouble(0)/buffer.getInt(1)
}
八、SparkSql的优化
1. 可以将df或者是ds进行缓存:
df.cache : 默认存储级别是:memory_and_disk
df.persist
底层是将df的数据缓存成柱状结构,在查询时,可以指定字段进行柱状扫描,提高查询效率
2. 调优参数:
3. 一些基本原则(九大原则):
参考:https://tech.meituan.com/2016/04/29/spark-tuning-basic.html
4. 数据倾斜的优化:
参考:https://tech.meituan.com/2016/05/12/spark-tuning-pro.html
什么叫数据倾斜:一个分区的数据要远远大于其他的分区的数据,那么整个应用程序的执行完成时间与这个分区处理的时间有关系,而且可能造成应用程序作业失败,或者是卡住不动了。
因此出现数据倾斜的时候,Spark作业看起来会运行得非常缓慢,甚至可能因为某个task处理的数据量过大导致内存溢出。
问题分析:具体问题具体分析
- 如何定位导致数据倾斜的代码?
数据倾斜只会发生在shuffle过程中。这里给大家罗列一些常用的并且可能会触发shuffle操作的算子:
distinct、groupByKey、reduceByKey、aggregateByKey、join、cogroup、repartition等。出现数据
倾斜时,可能就是你的代码中使用了这些算子中的某一个所导致的。
- 某个task执行特别慢的情况
首先要看的,就是数据倾斜发生在第几个stage中。
(1)yarn-client模式提交,那么本地是直接可以看到log的,可以在log中找到当前运行到了第几个stage
(2)yarn-cluster模式提交,则可以通过Spark Web UI来查看
- 某个task莫名其妙内存溢出的情况:
查看log中的异常
- 查看导致数据倾斜的key的数据分布情况
知道了数据倾斜发生在哪里之后,通常需要分析一下那个执行了shuffle操作并且导致了数据倾斜的RDD/Hive表,查看一下其中key的分布情况。
此时根据你执行操作的情况不同,可以有很多种查看key分布的方式:
1. 如果是Spark SQL中的group by、join语句导致的数据倾斜,那么就查询一下SQL中使用的表的key分布情况。
2. 如果是对Spark RDD执行shuffle算子导致的数据倾斜,那么可以在Spark作业中加入查看key分布的代码,比如RDD.countByKey()。然后对统计出来的各个key出现的次数,collect/take到客户端打印一下,就可以看到key的分布情况。
数据倾斜案例解决方案演示:
package com.qf.sql.day03
import org.apache.spark.sql.{DataFrame, SparkSession}
/**
* 案例演示:wordcount的统计,出现数据倾斜的解决方案- 局部聚合+全局聚合
*/
object _02DataSkewDemo01 {
def main(args: Array[String]): Unit = {
val spark: SparkSession = SparkSession.builder().appName("skew").master("local").getOrCreate()
import spark.implicits._
//获取一个scala集合对象
val list = List("a a a a a a a a a a a a a a a b b e c c e d f f","a a a b e f g c")
val df: DataFrame = list.toDF("line")
/*维护一个临时表*/
df.createTempView("temp")
/*分组统计每个单词的数量,可能会出现某一个单词过多的组*/
val sql =
"""
|select A.word,count(1)
|from
|(select
|explode(split(line," ")) word
|from temp
|)A
|group by A.word
|""".stripMargin
spark.sql(sql).show()
print("----先随机数字,作为前缀,添加到单词的前面-----")
val randSql =
"""
|select A.word,concat(floor(rand()*3),"_",A.word)
|from
|(
|select
|explode(split(line," ")) word
|from temp
|) A
|""".stripMargin
spark.sql(randSql).show()
print("----局部聚合: 将同一个key加上前缀,产生多个不同的key,然后相同前缀的key进行局部聚合-----")
val partSql =
"""
|select B.prefix_word,count(1) prefix_count
|from
|(
|select A.word,concat(floor(rand()*3),"_",A.word) prefix_word
|from
|(
|select
|explode(split(line," ")) word
|from temp
|) A
|) B
|group by B.prefix_word
|""".stripMargin
spark.sql(partSql).show()
println()
print("----去掉前缀,在完成全局聚合----")
val fullSql =
"""
|select substr(prefix_word,instr(prefix_word,"_")+1) f_word, sum(prefix_count)
|from
|(
|select B.prefix_word,count(1) prefix_count
|from
|(
|select A.word,concat(floor(rand()*3),"_",A.word) prefix_word
|from
|(
|select
|explode(split(line," ")) word
|from temp
|) A
|) B
|group by B.prefix_word
|) C
|group by f_word
|""".stripMargin
spark.sql(fullSql).show()
spark.stop()
}
}
九、SparkSql的运行架构
SparkSql执行的原理和数据库的sql类似,也使用到了词法/语法解析、绑定、优化、执行。
步骤如下:
1. 先进行词法/语法的校验: 语法是否正确,字段,表名是否存在等
校验成功后,会生成语法树(B+tree)
2. 在使用RuleExcutor将一些规则绑定到树的节点,生成逻辑计划。
3. 优化器在对逻辑计划进行优化工作,比如内存的优化,磁盘的优化
4. 优化后,会转成相应的JOB,然后开始执行(底层用到了sparkCode的DAGScheduler等)
Spark SQL由Core、Catalyst、Hive、Hive-ThriftServer四部分构成:
Core: 负责处理数据的输入和输出,如获取数据,查询结果输出成DataFrame等
Catalyst: 负责处理整个查询过程,包括解析、绑定、优化等
Hive: 负责对Hive数据进行处理
Hive-ThriftServer: 主要用于对hive的访问
Hive-ThriftServer: 主要用于对hive的访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KmHBfAzJ-1615727684989)(ClassNotes.assets/optimizer-1.png)]
LogicalPlan–>逻辑计划 Analyzer–>分析器 Optimizer–>优化器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6FGjFfk3-1615727684991)(ClassNotes.assets/optimizer-2.png)]
unresolved:未实现的含义
resolved:实现的含义
"""
|select B.prefix_word,count(1) prefix_count
|from
|(
|select A.word,concat(floor(rand()*3),"_",A.word) prefix_word
|from
|(
|select
|explode(split(line," ")) word
|from temp
|) A
|) B
|group by B.prefix_word
|""".stripMargin
spark.sql(partSql).show()
println()
print("----去掉前缀,在完成全局聚合----")
val fullSql =
"""
|select substr(prefix_word,instr(prefix_word,"_")+1) f_word, sum(prefix_count)
|from
|(
|select B.prefix_word,count(1) prefix_count
|from
|(
|select A.word,concat(floor(rand()*3),"_",A.word) prefix_word
|from
|(
|select
|explode(split(line," ")) word
|from temp
|) A
|) B
|group by B.prefix_word
|) C
|group by f_word
|""".stripMargin
spark.sql(fullSql).show()
spark.stop()
}
}
# 九、SparkSql的运行架构
SparkSql执行的原理和数据库的sql类似,也使用到了词法/语法解析、绑定、优化、执行。
步骤如下:
- 先进行词法/语法的校验: 语法是否正确,字段,表名是否存在等
校验成功后,会生成语法树(B+tree) - 在使用RuleExcutor将一些规则绑定到树的节点,生成逻辑计划。
- 优化器在对逻辑计划进行优化工作,比如内存的优化,磁盘的优化
- 优化后,会转成相应的JOB,然后开始执行(底层用到了sparkCode的DAGScheduler等)
Spark SQL由Core、Catalyst、Hive、Hive-ThriftServer四部分构成:
Core: 负责处理数据的输入和输出,如获取数据,查询结果输出成DataFrame等
Catalyst: 负责处理整个查询过程,包括解析、绑定、优化等
Hive: 负责对Hive数据进行处理
Hive-ThriftServer: 主要用于对hive的访问
Hive-ThriftServer: 主要用于对hive的访问
[外链图片转存中...(img-KmHBfAzJ-1615727684989)]
LogicalPlan-->逻辑计划 Analyzer-->分析器 Optimizer-->优化器
[外链图片转存中...(img-6FGjFfk3-1615727684991)]
unresolved:未实现的含义
resolved:实现的含义