赞
踩
Spark SQL 是Spark用于结构化数据处理的Spark模块
SparkSQL前身是Shark ,给熟悉RDBMS但又不理解MapReduce的技术人员提供快速上手的工具。
Hive 是早期唯一运行在Hadoop上的SQL-on-Hadoop 工具。但那时MapReduce计算过程中大量的中间磁盘落地过程消耗量大量的I/O,降低了运行效率,为了提高SQL-on-Hadoop,大量的工具开始产生,其中突出的是:
无缝的整合了SQL查询和Spark编程
使用相同的方式连接不同的数据源
在已有的仓库是直接运行SQL或者HiveQL
通过JDBC或者ODBC来连接
在Spark中,DataFrame是一种以RDD为基础的分布式数据集类似于传统数据库的二维表格。DataFrame与RDD主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。
同时,与Hive类似,DataFrame也支持嵌套数据类型(struct,array,map)。从API易用性来看,DataFrame APi提供的是一套高层的关系操作,比函数式RDD API要更加友好,门槛更低
DataFrame是为数据提供了Schema的视图。可以把它当做数据库中的一张表来对待
DataFrame也是懒执行的,但性能上比RDD要高,主要原因:优化的执行计划,即查询计划通过Spark catalyst optimiser进行优化。比如下面一个例子:
为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集。如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程。
DataSet 是分布式数据集合。DataSet是Spark 1.6中添加的一个新抽象,是DataFrame的一个扩展。它提供了RDD的优势(强类型,使用强大的lambda函数的能力)以及Spark SQL优化执行引擎的优点。DataSet也可以使用功能性的转换(操作map,flatMap,filter等等)。
Spark Core中,如果想要执行应用程序,需要首先构建上下文环境对象SparkContext,Spark SQL其实可以理解为对Spark Core的一种封装,不仅仅在模型上进行了封装,上下文环境对象也进行了封装。
在老的版本中,SparkSQL提供两种SQL查询起始点:一个叫SQLContext,用于Spark自己提供的SQL查询;一个叫HiveContext,用于连接Hive的查询。
SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContex和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了SparkContext,所以计算实际上是由sparkContext完成的。当我们使用 spark-shell 的时候, spark框架会自动的创建一个名称叫做spark的SparkSession对象, 就像我们以前可以自动获取到一个sc来表示SparkContext对象一样
SparkSQL的DataFrameAPI 允许我们使用DataFrame,而不用必须去注册临时表或者生成SQL表达式,DataFrameAPI既有 transformation操作也有action操作。
在SparkSQL中,SparkSession是创建DataFrame和执行SQL的入口,创建DataFrame有三种方式:通过Spark的数据源进行创建,从一个存在的RDD进行转换,还可以从HiveTable进行查询返回。
SQL语法风格是指我们查询数据的时候使用SQL语句来查询,这种风格的查询必须要有临时视图或者全局视图来辅助
DataFrame提供一个特定领域语言(domain-specific language, DSL)去管理结构化的数据。可以在 Scala, Java, Python 和 R 中使用 DSL,使用 DSL 语法风格不必去创建临时视图了
在IDEA中开发程序时,如果需要RDD与DF或者DS之间互相操作,那么需要引入 import spark.implicits._
这里的spark不是Scala中的包名,而是创建的sparkSession对象的变量名称,所以必须先创建SparkSession对象再导入。这里的spark对象不能使用var声明,因为Scala只支持val修饰的对象的引入。
spark-shell中无需导入,自动完成此操作。
实际开发中,一般通过样例类将RDD转换为DataFrame
DataFrame其实就是对RDD的封装,所以可以直接获取内部RDD
DataSet是具有强类型的数据集合,需要提供对应的类型信息。
SparkSQL能够自动将包含有case类的RDD转成DataSet,case类定义了table的结构,case类属性通过反射变成了表的列名。Case类可以包含诸如Seq或者Array等复杂结构
DataSet其实也是对RDD的封装,所以可以直接获取内部的RDD
DataFrame其实是DataSet的特例,所以他们可以互相转换
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_2.12</artifactId>
<version>3.0.0</version>
</dependency>
import org.apache.spark.sql.{DataFrame, Dataset, Row, SparkSession} import org.apache.spark.SparkConf import org.apache.spark.rdd.RDD // 定义样例类 case class User(id:Int,name:String,age:Int) object SparkSQL01_Demo { def main(args: Array[String]): Unit = { //创建上下文环境配置对象 val conf = new SparkConf().setMaster("local[*]").setAppName("SparkSQL") //创建SparkSession对象 val ss: SparkSession = SparkSession.builder().config(conf).getOrCreate() //RDD=>DataFrame=>DataSet转换需要引入隐式转换规则,否则无法转换 //spark不是包名,是上下文环境对象名 import ss.implicits._ //读取json文件 创建DataFrame val df: DataFrame = ss.read.json("F:/worksapce_java/bigdata/SparkSQL/data/person.json") //df.show //SQL风格 df.createOrReplaceTempView("user") //ss.sql("select * from user").show //DSL 风格语法 //df.select("username","age").show() //RDD与DataFrame,DataSet互换 val rdd1: RDD[(Int, String, Int)] = ss.sparkContext.makeRDD(List((1,"zhangsan",30),(2,"lisi",28),(3,"wangwu",20))) // RDD => DataFrame val df1: DataFrame = rdd1.toDF("id","name","age") //df1.show() // DataFrame => DataSet val ds1: Dataset[User] = df1.as[User] //ds1.show() // DataSet=>DataFrame val df2: DataFrame = ds1.toDF() //df2.show() //DataFrame => RDD 返回的RDD类型为Row,里面提供的getXXX方法可以获取字段值,类似jdbc处理结果集,但是索引从0开始 val rdd2: RDD[Row] = df2.rdd //rdd2.foreach(a=>println(a.getString(1))) //RDD=>DataSet val ds2: Dataset[User] = rdd1.map { case (id, name, age) => User(id, name, age) }.toDS() ds2.show() //DataSet => RDD val rdd3: RDD[User] = ds2.rdd ss.stop() } }
用户可以通过spark.udf功能添加自定义函数,实现自定义功能。
val df: DataFrame = ss.read.json("F:/worksapce_java/bigdata/SparkSQL/data/person.json")
//注册UDF
ss.udf.register("addName",(x:String)=>"Name:"+x)
//创建临时表
df.createOrReplaceTempView("people")
//应用UDF
ss.sql("select addName(name),age from people").show
强类型的Dataset和弱类型的DataFrame都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),max(),min()。除此之外,用户可以设定自己的自定义聚合函数。通过继承UserDefinedAggregateFunction来实现用户自定义弱类型聚合函数。从Spark3.0版本后,UserDefinedAggregateFunction已经不推荐使用了。可以统一采用强类型聚合函数Aggregator
接下来通过一个需求讲述什么是UDAF:需求:计算平均年龄
val conf: SparkConf = new SparkConf().setAppName("app").setMaster("local[*]")
val sc: SparkContext = new SparkContext(conf)
val res: (Int, Int) = sc.makeRDD(List(("zhangsan", 20), ("lisi", 30), ("wangw", 40))).map {
case (name, age) => {
(age, 1)
}
}.reduce {
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2)
}
}
println(res._1/res._2)
// 关闭连接
sc.stop()
class MyAC extends AccumulatorV2[Int,Int]{ var sum:Int = 0 var count:Int = 0 override def isZero: Boolean = { return sum ==0 && count == 0 } override def copy(): AccumulatorV2[Int, Int] = { val newMyAc = new MyAC newMyAc.sum = this.sum newMyAc.count = this.count newMyAc } override def reset(): Unit = { sum =0 count = 0 } override def add(v: Int): Unit = { sum += v count += 1 } override def merge(other: AccumulatorV2[Int, Int]): Unit = { other match { case o:MyAC=>{ sum += o.sum count += o.count } case _=> } } override def value: Int = sum/count }
/* 定义类继承UserDefinedAggregateFunction,并重写其中方法 */ class MyAveragUDAF extends UserDefinedAggregateFunction { // 聚合函数输入参数的数据类型 def inputSchema: StructType = StructType(Array(StructField("age",IntegerType))) // 聚合函数缓冲区中值的数据类型(age,count) def bufferSchema: StructType = { StructType(Array(StructField("sum",LongType),StructField("count",LongType))) } // 函数返回值的数据类型 def dataType: DataType = DoubleType // 稳定性:对于相同的输入是否一直返回相同的输出。 def deterministic: Boolean = true // 函数缓冲区初始化 def initialize(buffer: MutableAggregationBuffer): Unit = { // 存年龄的总和 buffer(0) = 0L // 存年龄的个数 buffer(1) = 0L } // 更新缓冲区中的数据 def update(buffer: MutableAggregationBuffer,input: Row): Unit = { if (!input.isNullAt(0)) { buffer(0) = buffer.getLong(0) + input.getInt(0) buffer(1) = buffer.getLong(1) + 1 } } // 合并缓冲区 def merge(buffer1: MutableAggregationBuffer,buffer2: Row): Unit = { buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0) buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1) } // 计算最终结果 def evaluate(buffer: Row): Double = buffer.getLong(0).toDouble / buffer.getLong(1) } 。。。 //创建聚合函数 var myAverage = new MyAveragUDAF //在spark中注册聚合函数 spark.udf.register("avgAge",myAverage) spark.sql("select avgAge(age) from user").show()
// TODO 创建UDAF函数 val udaf = new MyAvgAgeUDAF // TODO 注册到SparkSQL中 spark.udf.register("avgAge", functions.udaf(udaf)) // TODO 在SQL中使用聚合函数 // 定义用户的自定义聚合函数 spark.sql("select avgAge(age) from user").show // ************************************************** case class Buff( var sum:Long, var cnt:Long ) // totalage, count class MyAvgAgeUDAF extends Aggregator[Long, Buff, Double]{ override def zero: Buff = Buff(0,0) override def reduce(b: Buff, a: Long): Buff = { b.sum += a b.cnt += 1 b } override def merge(b1: Buff, b2: Buff): Buff = { b1.sum += b2.sum b1.cnt += b2.cnt b1 } override def finish(reduction: Buff): Double = { reduction.sum.toDouble/reduction.cnt } override def bufferEncoder: Encoder[Buff] = Encoders.product override def outputEncoder: Encoder[Double] = Encoders.scalaDouble }
SparkSQL提供了通用的保存数据和数据加载的方式,这里的通用指的是使用相同的API,根据不同的参数,读取和保存不同格式的数据,SparkSQL默认读取和保存的文件格式为parquet
文件路径
spark.sql("select * from json.`/opt/module/data/user.json`").show
Spark SQL的默认数据源为Parquet格式。Parquet是一种能够有效存储嵌套数据的列式存储格式。
数据源为Parquet文件时,Spark SQL可以方便的执行所有的操作,不需要使用format。修改配置项spark.sql.sources.default,可修改默认数据源格式。
Spark SQL 能够自动推测JSON数据集的结构,并将它加载为一个Dataset[Row]. 可以通过SparkSession.read.json()去加载JSON 文件。
**注意:Spark读取的JSON文件不是传统的JSON文件,每一行都应该是一个JSON串。**格式如下:
{"name":"Michael"}
{"name":"Andy", "age":30}
[{"name":"Justin", "age":19},{"name":"Justin", "age":19}]
Spark SQL可以配置CSV文件的列表信息,读取CSV文件,CSV文件的第一行设置为数据列
spark.read.format("csv").option("sep", ";").option("inferSchema", "true").option("header", "true").load("data/user.csv")
Spark SQL可以通过JDBC从关系型数据库中读取数据的方式创建DataFrame,通过对DataFrame一系列的计算后,还可以将数据再写回关系型数据库中。如果使用spark-shell操作,可在启动shell时指定相关的数据库驱动路径或者将相关的数据库驱动放到spark的类路径下。
bin/spark-shell
--jars mysql-connector-java-8.0.26-bin.jar
这里只学习Idea中通过JDBC对Mysql进行操作
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JDBC") val ss: SparkSession = SparkSession.builder().config(conf).getOrCreate() import ss.implicits._ //方式一 : 通用的load方法读取 ss.read.format("jdbc") .option("url", "jdbc:mysql://192.168.23.100:3306/spark_sql") .option("driver", "com.mysql.jdbc.Driver") .option("user","root") .option("password","123456") .option("dbtable","user") .load().show() //方式二 : 通用的load方法读取 参数另一种形式 ss.read.format("jdbc") .options(Map("url"->"jdbc:mysql://192.168.23.100:3306/spark_sql?user=root&password=123456", "dbtable"->"user","driver"->"com.mysql.jdbc.Driver")) .load().show() //方式三 : 使用jdbc方法读取 val prop = new Properties() prop.setProperty("user","root") prop.setProperty("password", "123456") val df: DataFrame = ss.read.jdbc("jdbc:mysql://192.168.23.100:3306/spark_sql","user",prop) df.show() ss.stop() }
case class User2(name: String, age: Long) def main(args: Array[String]): Unit = { val conf: SparkConf = new SparkConf().setMaster("local[*]").setAppName("JDBC") val ss: SparkSession = SparkSession.builder().config(conf).getOrCreate() import ss.implicits._ val rdd: RDD[User2] = ss.sparkContext.makeRDD(List(User2("王", 25), User2("赵", 26))) val ds: Dataset[User2] = rdd.toDS //方式一 : 通用的load方法读取 ds.write.format("jdbc") .option("url", "jdbc:mysql://192.168.23.100:3306/spark_sql") .option("driver", "com.mysql.jdbc.Driver") .option("user", "root") .option("charset", "utf8") .option("password", "123456") .option("dbtable", "user") .mode(SaveMode.Append).save() //方式二 : 通过jdbc方法 // val props: Properties = new Properties() // props.setProperty("user", "root") // props.setProperty("password", "123456") // ds.write.mode(SaveMode.Append).jdbc("jdbc:mysql://192.168.23.100:3306/spark_sql", "user", props) ss.stop() }
Apache Hive 是 Hadoop 上的 SQL 引擎,Spark SQL编译时可以包含 Hive 支持,也可以不包含。包含 Hive 支持的 Spark SQL 可以支持 Hive 表访问、UDF (用户自定义函数)以及 Hive 查询语言(HiveQL/HQL)等。需要强调的一点是,如果要在 Spark SQL 中包含Hive 的库,并不需要事先安装 Hive。一般来说,最好还是在编译Spark SQL时引入Hive支持,这样就可以使用这些特性了。如果你下载的是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。
若要把 Spark SQL 连接到一个部署好的 Hive 上,你必须把 hive-site.xml 复制到 Spark的配置文件目录中($SPARK_HOME/conf)。即使没有部署好 Hive,Spark SQL 也可以运行。 需要注意的是,如果你没有部署好Hive,Spark SQL 会在当前的工作目录中创建出自己的 Hive 元数据仓库,叫作 metastore_db。此外,如果你尝试使用 HiveQL 中的 CREATE TABLE (并非 CREATE EXTERNAL TABLE)语句来创建表,这些表会被放在你默认的文件系统中的 /user/hive/warehouse 目录中(如果你的 classpath 中有配好的 hdfs-site.xml,默认的文件系统就是 HDFS,否则就是本地文件系统)。
spark-shell默认是Hive支持的;代码中是默认不支持的,需要手动指定(加一个参数即可)。
<dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-hive_2.12</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-exec</artifactId> <version>1.2.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.27</version> </dependency>
case class User2(name: String, age: Long)
def main(args: Array[String]): Unit = {
val ss: SparkSession = SparkSession.builder().master("local[*]").appName("Hive").enableHiveSupport().getOrCreate()
import ss.implicits._
ss.sql("show databases ").show()
ss.stop()
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。