一、Spark SQL简介
1.背景
(1)HiveQL是将SQL语句转化为了MapReduce作业来执行,当使用Spark来代替MapReduce计算时,就变成了Hive on Spark(Shark),实现了将HiveQL翻译成Spark上的RDD操作。
(2)Shark一方面提高了SQL on Hadoop的性能,但是也导致了两个问题:①执行计划完全依赖于Hive;②Spark是线程级的,但是MapReduce是进程级的,所以导致Spark在兼容Hive的实现上存在线程安全问题,所以Shark也要维护一套打了补丁的Hive源码分支。
(3)2014年6月1日Shark项目和Spark SQL项目的主持人Reynold Xin宣布:停止对Shark的开发,团队将所有资源放在Spark SQL项目上,至此,Shark的发展画上了句话,但也因此发展出两个直线:Spark SQL和Hive on Spark。
①Spark SQL作为Spark生态的一员继续发展,而不再受限于Hive,只是兼容Hive。
②Hive on Spark是一个Hive的发展计划,该计划将Spark作为Hive的底层引擎之一,也就是说,Hive将不再受限于一个引擎,可以采用Map-Reduce、Tez、Spark等引擎。
二、Spark SQL主要内容
SparkSQL成为了专门处理结构化数据(比如DB,Json)的Spark组件
SparkSQL=Schema+RDD
1.Spark SQL架构
(1) Spark SQL在Hive兼容层面仅依赖HiveQL解析。
(2) Spark SQL执行计划的生成和优化都由Catays(函数式关系查询优化框架)负责。
(3) Spark SQL底层基于Spark Core(RDD)。
2.DataFrame(基于RDD)
DataFrame是以RDD为基础的分布式数据集,提供了详细的结构信息(RDD+Schema)。
DataFrame的推出,让Spark具备处理大规模结构化数据的能力,且比RDD方式更简单易用,而且具有更高的计算性能。
(1)创建DataFrame
通过已存在的RDD创建DataFrame、通过读取Hive表来创建DataFrame、其他数据源中创建DataFrame、由其他DataFrame转换而来。
spark 2.x后开始加入使用SparkSeesion。
提供了读写各种格式数据的API
(2)DataFrame小结
采用更高效的数据格式保存数据
使用列式存储格式(比如parquet)
使用分区(比如/year=2014/month=02/…)
使用统计数据自动跳过数据(比如min、max)
(3)数据的列式存储方式
parquet和orc
①可以跳过不符合条件的数据,只读取需要的数据,降低IO数据量
②压缩编码可以降低磁盘存储空间,进一步节约存储空间。
③只读取需要的列,支持向量运算,具备更好的扫描性能。
3.Spark SQL的性能
核心优化器:Catalyst
作用是将逻辑计划和物理计划的两次调用调优为一次调用
4.Dataset
扩展自DataFrame API(函数式编程风格、运行时检查、不能直接操作domain对象),提供了编译时类型安全,面向对象风格的API。
Dataset类型安全,可以直接作用于domain对象,在编译时进行类型检查,可以和DataFrame互相转换。
从范围上看DataSets是RDD和DataFrame的超集。
三、Spark SQL的简单应用
1.程序设计过程(此处只以计算FG% : 投篮命中率为例)
(1)创建SparkSession
import org.apache.spark.sql.{SQLContext, SparkSession}
object Z_score {
// (1) 分析2016年 ① ② ③ ④ 属性 z-score 排名
//用来分析的指标 ① FG% : 投篮命中率,② FT% :罚球命中率 ③ 3P: 三分球命中率 ④ TRB: 篮板球
def main(args: Array[String]): Unit = {
val ss=SparkSession.builder
.master("local")
.appName("Z-score")
.config("spark.debug.maxToStringFields", "100")
.config("spark.sql.crossJoin.enabled","true")//SparkSql不支持笛卡尔积操作,所以在这里要设置一下
.getOrCreate()
val datapath="hdfs://master:9000/user/spark/data/basketball/leagues_NBA_2016_per_game_per_game.csv"
//读取2016年数据到dataframe中
var playerdata=ss.read.format("csv")
.option("header",true)//文件有表头
.option("inferSchema",true.toString)//自动推断属性列的数据类型
.load(datapath)
.na.fill(0)//将null值均用0补齐
//选择需要的数据
playerdata=playerdata.select("Rk","Player","Age","FG%","3P13","FT%","TRB")
playerdata=playerdata.withColumnRenamed("FG%","FGrate")
playerdata=playerdata.withColumnRenamed("FT%","FTrate")
(2)创建DataFrame或DataSet
(3)在DataFrame或DataSet上进行转换或action操作
//①FG%
计算所有球员该数据均值
val calmeanFG=playerdata.agg("Rk"->"max","FG%"->"sum")
var meanFG=calmeanFG.select(calmeanFG("sum(FG%)")/calmeanFG("max(Rk)"))
meanFG=meanFG.withColumnRenamed("(sum(FG%) / max(Rk))","meanFG%")
//作笛卡尔积操作(将均值和标准差添加入dataframe中便于操作)
var fg=playerdata.join(FGdata)
fg=fg.select(fg("Player"),fg("Age"),(fg("FGrate")-fg("meanFG"))/fg("stdFG"))
fg=fg.sort(-fg("((FGrate - meanFG) / stdFG)")).withColumnRenamed("((FGrate - meanFG) / stdFG)","z_FG")
fg.show(10)
(4)然后返回结果