SparkSQL特点
- 写更少的代码
- 读更少的数据
- 提供更好的性能(字节码生成技术、SQL优化)
SparkSQL数据抽象
-
DataSet会逐步取代 RDD 和 DataFrame 成为唯一的API接口
-
DataFrame
-
前身是SchemaRDD。Spark1.3更名为DataFrame。不继承RDD,自己实现了RDD的大部分功能
-
与RDD类似,DataFrame也是一个分布式数据集:
- DataFrame可以看做分布式 Row 对象的集合,提供了由列组成的详细模式信息,使其可以得到优化。
- DataFrame 不仅有比RDD更多的算子,还可以进行执行计划的优化
- DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即schema
- DataFrame也支持嵌套数据类型(struct、array和map)
- DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低
- Dataframe的劣势在于在编译期缺少类型安全检查,导致运行时出错
DataSet
-
与RDD相比,保存了更多的描述信息,概念上等同于关系型数据库中的二维表
-
与DataFrame相比,保存了类型信息,是强类型的,提供了编译时类型检查
-
调用Dataset的方法先会生成逻辑计划,然后Spark的优化器进行优化,最终生成物理计划,然后提交到集群中运行。
-
DataFrame表示为DataSet[Row],即DataSet的子集
-
Row & Schema
-
DataFrame = RDD[Row] + Schema;DataFrame 的前身是 SchemaRDD
-
三者共性
-
1、RDD、DataFrame、Dataset都是 Spark 平台下的分布式弹性数据集,为处理海量数据提供便利
-
2、三者都有许多相同的概念,如分区、持久化、容错等;有许多共同的函数,如map、filter,sortBy等
-
3、三者都有惰性机制,只有在遇到 Action 算子时,才会开始真正的计算
-
4、对DataFrame和Dataset进行操作许多操作都需要这个包进行支持, import spark.implicits._
三者区别
-
DataFrame(DataFrame = RDD[Row] + Schema):
- 1、与RDD和Dataset不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值
- 2、DataFrame与Dataset均支持 SparkSQL 的操作
-
Dataset(Dataset = RDD[case class].toDS):
- 1、Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同;
- 2、DataFrame 定义为 Dataset[Row]。每一行的类型是Row,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用前面提到的getAS方法或者模式匹配拿出特定字段;
- 3、Dataset每一行的类型都是一个case class,在自定义了case class之后可以很自由的获得每一行的信息;
SparkSession
- 在 Spark 2.0 之后:将数据入口点统一到了SparkSession,SparkSession 封装了 SqlContext 及 HiveContext;实现了 SQLContext 及 HiveContext 所有功能;
DataFrame & DataSet的创建
- 1、由range生成Dataset
- 2、由集合生成Dataset Dataset = RDD[case class]
- 3、由集合生成DataFrame DataFrame = RDD[Row] + Schema
- 4、RDD 转成 DataFrame DataFrame = RDD[Row] + Schema
- 5、RDD转Dataset Dataset = RDD[case class] | DataFrame = RDD[Row] + Schema
- 6、从文件创建DateFrame
三者的转换
Action操作
-
与RDD类似的操作
-
show、collect、collectAsList、head、first、count、take、takeAsList、reduce
-
与结构相关
-
printSchema、explain、columns、dtypes、col
Transformation操作
-
与RDD类似的操作
-
map、filter、flatMap、mapPartitions、sample、 randomSplit、 limit、distinct、dropDuplicates、describe
-
存储相关
-
cacheTable、persist、checkpoint、unpersist、cache
-
Dataset 默认的存储级别是 MEMORY_AND_DISK
-
select相关
-
列的多种表示、select、selectExpr
- // 列的多种表示方法。使用""、$""、'、col()、ds("")
// 注意:不要混用;必要时使用spark.implicitis._;并非每个表示在所有的地方都有效
df1.select($"ename", $"hiredate", $"sal").show
df1.select("ename", "hiredate", "sal").show
df1.select('ename, 'hiredate, 'sal).show
df1.select(col("ename"), col("hiredate"), col("sal")).show
df1.select(df1("ename"), df1("hiredate"), df1("sal")).show
// 下面的写法无效,其他列的表示法有效
df1.select("ename", "hiredate", "sal"+100).show
df1.select("ename", "hiredate", "sal+100").show
// 这样写才符合语法
df1.select($"ename", $"hiredate", $"sal"+100).show
df1.select('ename, 'hiredate, 'sal+100).show
// 可使用expr表达式(expr里面只能使用引号)
df1.select(expr("comm+100"), expr("sal+100"), expr("ename")).show
df1.selectExpr("ename as name").show
df1.selectExpr("power(sal, 2)", "sal").show
df1.selectExpr("round(sal, -3) as newsal", "sal", "ename").show
- drop、withColumn、withColumnRenamed、cast(内置函数)
-
where相关 ==filter
-
groupBy相关
-
groupBy、agg、max、min、avg、sum、count(后面5个为内置函数)
-
orderBy相关 ==sort
-
join相关
- df1.crossJoin(df1).count // 1、笛卡尔积
- df1.join(df1, "empno").count // 2、等值连接(单字段)(连接字段empno,仅显示了一次)
df1.join(df1, Seq("empno", "ename")).show // 3、等值连接(多字段)(连接字段empno、ename,仅显示了一次)
-
注意 DS在join操作之后变成了DF
-
集合相关
- union==unionAll(过期)求并集,不去重、intersect求交 、except求差
-
空值处理
-
na.fill、na.drop
-
窗口函数
-
一般情况下窗口函数不用 DSL 处理,直接用SQL更方便
-
参考源码Window.scala、WindowSpec.scala(主要)
-
内建函数
-
http://spark.apache.org/docs/latest/api/sql/index.html
SQL语句
- SparkSQL与HQL兼容;与HQL相比,SparkSQL更简洁
- createTempView、createOrReplaceTempView、spark.sql("SQL")
输入 & 输出
- SparkSQL内建支持的数据源包括:Parquet、JSON、CSV、Avro、Images、BinaryFiles(Spark 3.0)。
- Parquet是默认的数据源
- df.write.format("parquet")
.mode("overwrite")
.option("compression", "snappy")
.save("data/parquet")
- json文件
- spark.sql("SELECT * FROM emp").write
.format("json")
.mode("overwrite")
.save("data/json")
-
CSV文件
-
注意:如果有中文注意表的字符集,否则会有乱码
-
SaveMode.ErrorIfExists(默认)。若表存在,则会直接报异常,数据不能存入数据库
-
SaveMode.Append。若表存在,则追加在该表中;若该表不存在,则会先创建表,再插入数据
-
SaveMode.Overwrite。先将已有的表及其数据全都删除,再重新创建该表,最后插入新的数据
-
SaveMode.Ignore。若表不存在,则创建表并存入数据;若表存在,直接跳过数据的存储,不会报错
UDF & UDAF
-
UDF(User Defined Function),自定义函数。函数的输入、输出都是一条数据记录
-
UDF可以放到SQL语句的fields部分,也可以作为where、groupBy或者having子句的一部分
-
UDAF(User Defined Aggregation Funcation),用户自定义聚合函数。函数本身作用于数据集合,能够在聚合操作的基础上进行自定义操作(多条数据输入,一条数据输出);类似于在group by之后使用的sum、avg等函数;
-
Spark为所有的UDAF定义了一个父类UserDefinedAggregateFunction 。要继承这个类,需要实现父类的几个抽象方法
-
inputSchema用于定义与DataFrame列有关的输入样式
-
bufferSchema用于定义存储聚合运算时产生的中间数据结果的Schema
-
dataType标明了UDAF函数的返回值类型
-
deterministic是一个布尔值,用以标记针对给定的一组输入,UDAF是否总是生成相同的结果
-
initialize对聚合运算中间结果的初始化
-
update函数的第一个参数为bufferSchema中两个Field的索引,默认以0开始;UDAF的核心计算都发生在
-
update函数中;update函数的第二个参数input: Row对应的并非DataFrame的行,而是被inputSchema投影了的行
-
merge函数负责合并两个聚合运算的buffer,再将其存储到MutableAggregationBuffer中
-
evaluate函数完成对聚合Buffer值的运算,得到最终的结果
-
UDAF分为类型安全和类型不安全两种
访问Hive
- 最好使用 metastore service 连接Hive;使用直连 metastore 的方式时,SparkSQL程序会修改 Hive 的版本信息;
SparkSQL原理
SparkSQL中的五种 Join 策略 join
-
Broadcast hash join (BHJ)
-
实现原理是将小表的数据广播到 Spark 所有的 Executor 端,这个广播过程和我们自己去广播数据没什么区别。实现过程:
- 利用collect算子将小表的数据从Executor端拉取到Driver端
- 在 Driver 端调用 sparkContext.broadcast 广播到所有 Executor 端
- 在 Executor 端使用广播的数据与大表进行 Join 操作(实际上是执行map操作)
-
这种 Join 策略避免了 Shuffle 操作,一般而言比其他join策略执行要快
-
这种jion满足的条件:
- 小表的数据必须很小,可以通过 spark.sql.autoBroadcastJoinThreshold 参数来配置,默认是 10MB
- 如果内存比较大,可以将阈值适当加大
- 将 spark.sql.autoBroadcastJoinThreshold 参数设置为 -1,可以关闭这种连接方式
- 只能用于等值 Join,不要求参与 Join 的 keys 可排序
-
Shuffle hash join(SHJ)
-
当表中的数据比较大,又不适合使用广播,这个时候就可以考虑使用 Shuffle Hash Join
-
Shuffle Hash Join 同样是在大表和小表进行 Join 的时候选择的一种策略。
-
计算思想:把大表和小表按照相同
的分区算法和分区数进行分区(根据参与 Join 的 keys 进行分区),这样就保证了 hash 值一样的数据都分发到同一
个分区中,然后在同一个 Executor 中两张表 hash 值一样的分区就可以在本地进行 hash Join 了。在进行 Join 之
前,还会对小表的分区构建 Hash Map。Shuffle hash join 利用了分治思想,把大问题拆解成小问题去解决。 -
要启用 Shuffle Hash Join 必须满足以下条件:
- 只支持等值 Join
- spark.sql.join.preferSortMergeJoin 参数必须为 false,该参数自Spark 2.0.0 版本引入的,默认值为true。
- 小表的大小(plan.stats.sizeInBytes)必须小于 spark.sql.autoBroadcastJoinThreshold * spark.sql.shuffle.partitions(默认值200)
- 而且小表大小(stats.sizeInBytes)的三倍必须小于等于大表的大小(stats.sizeInBytes),即a.stats.sizeInBytes * 3 < = b.stats.sizeInBytes
-
Shuffle sort merge join (SMJ)
-
前面两种 Join 策略对表的大小都有条件的,如果参与 Join 的表都很大,这时候就得考虑用 Shuffle Sort Merge Join了。
-
Shuffle Sort Merge Join 的实现思想:
- 将两张表按照 join key 进行shuffle,保证join key值相同的记录会被分在相应的分区
- 对每个分区内的数据进行排序
- 排序后再对相应的分区内的记录进行连接
-
满足条件:仅支持等值 Join,并且要求参与 Join 的 Keys 可排序
-
Shuffle-and-replicate nested loop join,又称笛卡尔积(Cartesian product join)
-
两张参与 Join 的表没指定连接条件
-
Broadcast nested loop join (BNLJ)
-
Broadcast nested loop join 在某些情况会对某张表重复扫描多次,效率非常低下
-
支持等值和不等值 Join,支持所有的 Join 类型
SQL解析过程
-
Spark SQL对SQL语句的处理和关系型数据库类似,即词法/语法解析、绑定、优化、执行。
-
Spark SQL会先将SQL语句解析成一棵树,然后使用规则(Rule)对Tree进行绑定、优化等处理过程。
-
Spark SQL由Core、Catalyst、Hive、Hive-ThriftServer四部分构成:
-
Core: 负责处理数据的输入和输出,如获取数据,查询结果输出成DataFrame等
-
Catalyst: 负责处理整个查询过程,包括解析、绑定、优化等
-
Hive: 负责对Hive数据进行处理
-
Hive-ThriftServer: 主要用于对Hive的访问
-
SparkSQL 上的多种resolve 规则:
-
ResolverRelations。解析表(列)的基本类型等信息
-
ResolveFuncions。解析出来函数的基本信息
-
ResolveReferences。解析引用,通常是解析列名