【Spark】SparkSQL入门解析(一)

【Spark】SparkSQL入门解析(一)_第1张图片

【一】简介 (本文部分图片来自网络,侵删)

Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用

【Spark】SparkSQL入门解析(一)_第2张图片

【二】特点

【2.1】易整合

Spark SQL可以使用SQL或熟悉的DataFrame API在Spark程序中查询结构化数据,可在Java,Scala,Python和R中使用

【Spark】SparkSQL入门解析(一)_第3张图片

【2.2】统一的数据访问方式

DataFrame和SQL提供了一种访问各种数据源的通用方法,包括Hive,Avro,Parquet,ORC,JSON和JDBC。甚至可以跨这些源联接数据

【Spark】SparkSQL入门解析(一)_第4张图片

【2.3】兼容Hive

Spark SQL支持HiveQL语法以及Hive SerDes和UDF,从而访问现有的Hive仓库

【Spark】SparkSQL入门解析(一)_第5张图片

【2.4】标准的数据连接

服务器模式为商业智能工具提供了行业标准的JDBC和ODBC连接
【Spark】SparkSQL入门解析(一)_第6张图片


【三】概述

【3.1】SparkSQL可以看做是一个转换层,向下对接各种不同的结构化数据源,向上提供不同的数据访问方式

【Spark】SparkSQL入门解析(一)_第7张图片


【3.2】

在SparkSQL中Spark为我们提供了两个新的抽象,分别是DataFrame和DataSet;他们和RDD有什么区别呢?首先从版本的产生上来看:RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式

注意:在后期的Spark版本中,DataSet会逐步取代RDD和DataFrame成为唯一的API接口

【Spark】SparkSQL入门解析(一)_第8张图片

【3.3】RDD

RDD概述
  • 【1】RDD意为弹性分布式数据集,是一个懒执行的、不可变的可以支持Lambda表达式的并行数据集合
  • 【2】RDD的最大好处就是简单,API的人性化程度很高
  • 【3】RDD的劣势是性能限制,它是一个JVM驻内存对象,这也就决定了存在GC的限制和数据增加时Java序列化成本的升高

【3.4】DataFrame

DataFrame概述
  • 【1】与RDD类似,DataFrame也是一个分布式数据数据集。然而DataFrame更像传统数据库的二维表格,除了数据以外,还记录数据的结构信息,即Schema信息
  • 【2】与Hive类似,DataFrame也支持嵌套数据类型(struct、array和map)
  • 【3】从API易用性的角度上看,DataFrame API提供的是一套高层的关系操作,比函数式的RDD API要更加友好,门槛更低。由于与R和Pandas的DataFrame类似,Spark DataFrame很好地继承了传统单机数据分析的开发体验
    【Spark】SparkSQL入门解析(一)_第9张图片
  • 【1】上图直观地体现了DataFrame和RDD的区别。左侧的RDD[Person]虽然以Person为类型参数,但Spark框架本身不了解Person类的内部结构。而右侧的DataFrame却提供了详细的结构信息,使得Spark SQL可以清楚地知道该数据集中包含哪些列,每列的名称和类型各是什么。DataFrame多了数据的结构信息,即Schema。RDD是分布式的Java对象的集合,DataFrame是分布式的Row对象的集合。DataFrame除了提供了比RDD更丰富的算子以外,更重要的特点是提升执行效率减少数据读取以及执行计划的优化,比如filter下推、裁剪等…
  • 【2】DataFrame是为数据提供了Schema的视图,可以把它当做数据库中的一张表来对待
  • 【3】DataFrame也是懒执行的,性能上比RDD要高,主要有两方面原因:
    ①.定制化内存管理
    ②.数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了GC的限制
    【Spark】SparkSQL入门解析(一)_第10张图片
  • 【1】优化的执行计划 ,查询计划通过Spark catalyst optimiser进行优化

【Spark】SparkSQL入门解析(一)_第11张图片

  • 【2】比如下面一个例子
    在这里插入图片描述
    【Spark】SparkSQL入门解析(一)_第12张图片
  • 【3】为了说明查询优化,我们来看上图展示的人口数据分析的示例。图中构造了两个DataFrame,将它们join之后又做了一次filter操作。如果原封不动地执行这个执行计划,最终的执行效率是不高的。因为join是一个代价较大的操作,也可能会产生一个较大的数据集
    如果我们能将filter下推到 join下方,先对DataFrame进行过滤,再join过滤后的较小的结果集,便可以有效缩短执行时间。而Spark SQL的查询优化器正是这样做的。简而言之,逻辑查询计划优化就是一个利用基于关系代数的等价变换,将高成本的操作替换为低成本操作的过程
    得到的优化执行计划在转换成物理执行计划的过程中,还可以根据具体的数据源的特性将过滤条件下推至数据源内。最右侧的物理执行计划中Filter之所以消失不见,就是因为溶入了用于执行最终的读取操作的表扫描节点内
    对于普通开发者而言,查询优化器的意义在于,即便是经验并不丰富的程序员写出的次优的查询,也可以被尽量转换为高效的形式予以执行
  • 【6】DataFrame的劣势在于在编译期缺少类型安全检查,导致运行时出错

【3.5】DataSet

DataSet概述
  • 【1】DataSet是Dataframe API的一个扩展,是Spark最新的数据抽象
  • 【2】DataSet用户友好的API风格,既具有类型安全检查也具有DataFrame的查询优化特性
  • 【3】DataSet支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率
  • 【4】样例类被用来在DataSet中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称
  • 【5】DataFrame和DataSet之间可以互相转换,DataFrame/DataSet[Row] + 类型 =DataSet [类型] ,所以可以通过 as 方法将DataFrame转换为DataSet,也可以通过 toDF 方法将DataSet转换为DataFrame
  • 【6】DataSet是强类型的,比如可以有DataSet[Car],DataSet[Person]…

DataFrame只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个String进行减法操作,在执行的时候才报错,而DataSet不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟JSON对象和类对象之间的类比,如下图:
【Spark】SparkSQL入门解析(一)_第13张图片

注意:RDD让我们能够决定怎么做,而DataFrame和DataSet让我们决定做什么,控制的粒度不一样

【Spark】SparkSQL入门解析(一)_第14张图片

【3.6】RDD、DataFrame和DataSet三者的共性

  • 【1】RDD、DataFrame、DataSet全都是Spark平台下的分布式弹性数据集,为处理超大型数据提供便利
  • 【2】三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在Action中使用对应的结果,在执行时会被直接跳过
	val sparkconf = new SparkConf().setMaster("local[*]").setAppName("sparkSqlDemo")
	val spark = SparkSession.builder().config(sparkconf).getOrCreate()
	val rdd = spark.sparkContext.parallelize(Seq(("a", 1), ("b", 1), ("a", 1)))
	// 下面这个map并不会运行
	rdd.map{line=>
	  println("运行到这里啦...")
	  line._1
	}
  • 【3】三者都会根据Spark的内存情况自动缓存运算,这样即使数据量很大,也不用担心会内存溢出
  • 【4】三者都有partition的概念
  • 【5】三者有许多共同的函数,如filter,排序等…
  • 【6】在对DataFrame和DataSet进行操作许多操作都需要导入下方隐式函数的支持
注意:spark不是包名,是SparkSession的名字
import spark.implicits._
  • 【7】DataFrame和DataSet均可使用模式匹配获取各个字段的值和类型

DataFrame:

	df.map {
		case Row(id: Int, name: String, age: Int) => {
		   println(s"${id},${name},${age}")
		   name
		 }
		 case _ => ""
	}

DataSet:

	ds.map {
		 case User(id: Int, name: String, age: Int) => {
		    println(s"${id},${name},${age}")
		    name
		  }
		  case _ => ""
	}

【3.7】RDD、DataFrame和DataSet三者的区别

【1】RDD:

RDD可以设置类型参数,但RDD并不了解其内部结构

【2】DataFrame:

1.与RDD和DataSet不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值,如下:
	df.foreach{
	      line => {
	        val id = line.getAs[Int]("id")
	        val name = line.getAs[String]("name")
	        val age = line.getAs[Int]("age")
	        println(s"${id} - ${name} - ${age}")
	        val id2 = line.getAs[Int](0)
	        val name2 = line.getAs[String](1)
	        val age2 = line.getAs[Int](2)
	        println(s"${id2} -- ${name2} -- ${age2}")
	      }
	 }
2.DataFrame与DataSet一般与 Spark ml 同时使用

3.DataFrame与DataSet均支持 SparkSql 的操作,比如select,groupby之类,
还能注册临时表/视窗,进行sql语句操作,如下:
	df.createOrReplaceTempView("user")
	spark.sql("select id,name,age from user").show()
4.DataFrame 与 DataSet 支持一些特别方便的保存方式,默认读取和输出的格式都是 parquet 格式
也可以保存成json、csv等格式...  如下:
	【读取】
	1.读取数据通用方式1:读取json格式
	val df:DataFrame = spark.read.json("input/user.json") 
	或 val df:DataFrame = spark.read.format("json").load("input/user.json")
	2.读取数据默认方式2:读取parquet格式
	val df:DataFrame = spark.read.load("input/user.parquet")
	【保存】
	1.保存数据通用方式1:保存为指定格式,例如:json
	df.write.format("csv").mode(SaveMode.Overwrite).save("outputcsv")
	df.write.format("json").mode("append").save("outputjson")
	2.保存数据默认方式2:保存为默认parquet格式,如果文件夹存在则覆盖
	df.write.mode(SaveMode.Overwrite).save("outputparquet")

【3】DataSet:

1.DataSet 和 DataFrame 拥有完全相同的成员函数,区别只是每一行的数据类型不同
2.DataFrame 也可以叫 DataSet[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,
  只能用上面提到的 getAS 方法或者三者共性中的第七条提到的模式匹配拿出特定字段
3.在DataSet中,每一行是什么类型是不一定的,在自定义了case class样例类之后可以很自由的获得每一行的信息
4.可以看出,DataSet在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,
如果使用DataSet,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即DataSet[Row]就能比较好的解决问题

【3.8】三者的相互转换

【Spark】SparkSQL入门解析(一)_第15张图片

【1】 RDD + 结构 = DataFrame

方法:rdd.toDF(字段…)

在这里插入图片描述

【2】DataFrame + 类型 = DataSet

方法:df.as[类型]

【Spark】SparkSQL入门解析(一)_第16张图片

【3】RDD + 结构 + 类型 = DataSet (RDD + 类(样例类) = DataSet)

方法:rdd.toDS

【Spark】SparkSQL入门解析(一)_第17张图片

【4】DataSet - 类型 = DataFrame

方法:ds.toDF

【Spark】SparkSQL入门解析(一)_第18张图片

【5】DataFrame - 结构 = RDD

方法:df.rdd

在这里插入图片描述

【四】SparkSQL解析

【1】新的起始点SparkSession

  • 在老的版本中,SparkSQL提供两种SQL查询起始点,一个叫SQLContext,用于Spark自己提供的SQL查询,一个叫HiveContext,用于连接Hive的查询,SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContext和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了SparkContext,所以计算实际上是由SparkContext完成的
  • SparkSession.builder 用于创建一个SparkSession
    import spark.implicits._的引入是用于将DataFrames隐式转换成RDD,使DataFream、DataSet能够使用RDD中的方法

【2】IDEA创建SparkSQL程序

1.IDEA中程序的打包和运行方式都和SparkCore类似,Maven依赖中需要添加新的依赖项:
<dependency>
    <groupId>org.apache.spark</groupId>
    <artifactId>spark-sql_2.11</artifactId>
    <version>${spark.version}</version>
    <scope>provided</scope>
</dependency>
2.HelloWorld :
object HelloWorld {

  def main(args: Array[String]) {
    //创建SparkSession 
    val spark = SparkSession
      .builder()
      .master("local[*]") 
      .appName("sparkDemo")
      .config(new SparkConf())
      .getOrCreate()
    // For implicit conversions like converting RDDs to DataFrames
    import spark.implicits._
    val df = spark.read.json("input/user.json")
    // Displays the content of the DataFrame to stdout
    df.show()
    df.filter($"age" > 21).show()
    df.createOrReplaceTempView("user")
    spark.sql("SELECT * FROM user where age > 21").show()
    spark.stop()
  }
}
3.创建DataFrame:
  • 在Spark SQL中SparkSession是创建DataFrames和执行SQL的入口,创建DataFrames有三种方式,一种是可以从一个存在的RDD进行转换,还可以从Hive Table进行查询返回,或者通过Spark的数据源进行创建
1、从Spark数据源进行创建:
val df = spark.read.json("examples/src/main/resources/people.json")
2、从RDD进行转换:
val peopleRdd = sc.textFile("examples/src/main/resources/people.txt")
val peopleDF = peopleRdd.map(_.split(",")).map(paras => (paras(0),paras(1).trim().toInt)).toDF("name","age")
4.DataFrame常用操作:
  • 【1】DSL风格语法
val df = spark.read.json("examples/src/main/resources/people.json")
df.show()
import spark.implicits._
df.printSchema()
df.select("name").show()
df.select($"name", $"age" + 1).show()
df.filter($"age" > 21).show()
df.groupBy("age").count().show()
  • 【2】SQL风格语法
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
df.createGlobalTempView("people")
spark.sql("SELECT * FROM global_temp.people").show()
spark.newSession().sql("SELECT * FROM global_temp.people").show()

注意:临时表是Session范围内的,Session退出后,表就失效了。
如果想应用范围内有效,可以使用全局表。注意使用全局表时需要全路径访问,如:global_temp.people
5.DataSet:

Dataset是具有强类型的数据集合,需要提供对应的类型信息

import spark.implicits._
case class Person(name: String, age: Long)
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
6.DataSet和RDD互操作

Spark SQL支持通过两种方式将存在的RDD转换为DataSet,转换的过程中需要让DataSet获取RDD中的Schema信息,主要有两种方式,一种是通过反射来获取RDD中的Schema信息。这种方式适合于列名已知的情况下。第二种是通过编程接口的方式将Schema信息应用于RDD,这种方式可以处理那种在运行时才能知道列的方式

①.通过反射获取Scheam
SparkSQL能够自动将包含有case类的RDD转换成DataSet,case类定义了table的结构,case类属性通过反射变成了表的列名。case类可以包含诸如Seqs或者Array等复杂的结构

import spark.implicits._
val peopleDF = spark.sparkContext
.textFile("examples/src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
peopleDF.createOrReplaceTempView("people")
val teenagersDF = spark.sql("SELECT name, age FROM people WHERE age BETWEEN 13 AND 19")
teenagersDF.map(teenager => "Name: " + teenager(0)).show()
teenagersDF.map(teenager => "Name: " + teenager.getAs[String]("name")).show()
implicit val mapEncoder = org.apache.spark.sql.Encoders.kryo[Map[String, Any]]
teenagersDF.map(teenager => teenager.getValuesMap[Any](List("name", "age"))).collect()

②.通过编程设置Schema(StructType)
如果case类不能够提前定义,可以通过下面三个步骤定义一个DataFrame
【1】、创建一个多行结构的RDD;
【2】、创建用StructType来表示的行结构信息
【3】、通过SparkSession提供的createDataFrame方法来应用Schema

import spark.implicits._
import org.apache.spark.sql.types._
val peopleRDD = spark.sparkContext.textFile("examples/src/main/resources/people.txt")
val schema = StructType(Seq(
StructField("name",StringType,true),
StructField("age",StringType,true)
))
import org.apache.spark.sql._
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim))
val peopleDF = spark.createDataFrame(rowRDD, schema)
peopleDF.createOrReplaceTempView("people")
val results = spark.sql("SELECT name FROM people")
results.map(attributes => "Name: " + attributes().show()
7.用户自定义函数
通过spark.udf功能用户可以自定义函数

①.用户自定义UDF函数

val df = spark.read.json("examples/src/main/resources/people.json")
df.show()
spark.udf.register("addName", (x:String)=> "Name:"+x)
df.createOrReplaceTempView("people")
spark.sql("Select addName(name), age from people").show()

②.用户自定义聚合函数

强类型的Dataset和弱类型的DataFrame都提供了相关的聚合函数, 如 count(),countDistinct(),avg(),max(),min()。除此之外,用户可以设定自己的自定义聚合函数
弱类型:
object SparkSQL_UDAF1 {

  def main(args: Array[String]): Unit = {
    //创建SparkSQL环境对象
    val spark = SparkSession
      .builder()
      .master("local[*]")
      .appName("SparkSQL05_UDAF")
      .config(new SparkConf())
      .getOrCreate()
    import spark.implicits._
    //自定义聚合函数
    val udaf = new MyAgeAvgFunction
    //注册聚合函数
    spark.udf.register("avgAge",udaf)
    val df: DataFrame = spark.read.json("input/user.json")
    df.createOrReplaceTempView("user")
    spark.sql("select avgAge(age) from user").show()
    //释放资源
    spark.stop()
  }
  //自定义聚合函数(弱类型)
  //1.继承UserDefinedAggregateFunction
  //2.实现方法
  
  class MyAgeAvgFunction extends UserDefinedAggregateFunction{
    //函数输入的数据结构
    override def inputSchema: StructType = new StructType().add("age",LongType)
    //计算时的数据结构
    override def bufferSchema: StructType = new StructType().add("sum",LongType).add("count",LongType)
    //函数返回的数据类型
    override def dataType: DataType = DoubleType
    //函数是否稳定
    override def deterministic: Boolean = true
    //计算之前,缓冲区的初始化
    override def initialize(buffer: MutableAggregationBuffer): Unit = {
      //sum
      buffer(0) = 0L
      //count
      buffer(1) = 0L
    }
    //根据查询结果更新缓冲区数据
    override def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
      //sum
      buffer(0) = buffer.getLong(0) + input.getLong(0)
      //count
      buffer(1) = buffer.getLong(1) + 1
    }
    //将多个节点的缓冲区合并
    override def merge(buffer1: MutableAggregationBuffer, buffer2: Row): Unit = {
      //sum
      buffer1(0) = buffer1.getLong(0) + buffer2.getLong(0)
      //count
      buffer1(1) = buffer1.getLong(1) + buffer2.getLong(1)
    }
    //计算最终结果
    override def evaluate(buffer: Row): Any = {
      buffer.getLong(0).toDouble / buffer.getLong(1)
    }
  }
}
强类型:
object SparkSQL_UDAF2 {

  def main(args: Array[String]): Unit = {
    //创建SparkSQL环境对象
    val spark = SparkSession
      .builder()
      .master("local[*]")
      .appName("SparkSQL06_UDAF")
      .config(new SparkConf())
      .getOrCreate()
    import spark.implicits._
    //自定义聚合函数
    val udaf = new MyAgeAvgFunction
    //将聚合函数转换为查询列
    val avgCol: TypedColumn[UserBean, Double] = udaf.toColumn.name("avgAge")
    val df: DataFrame = spark.read.json("input/user.json")
    val ds = df.as[UserBean]
    //应用函数
    ds.select(avgCol).show()
    //释放资源
    spark.stop()
  }
  case class UserBean(name: String, age: BigInt)
  case class AvgBuffer(var sum: BigInt, var count: Int)

  //自定义聚合函数(强类型)
  //1.继承Aggregator,设置泛型
  //2.实现方法
  
  class MyAgeAvgFunction extends Aggregator[UserBean, AvgBuffer, Double] {
    //初始化
    override def zero: AvgBuffer = AvgBuffer(0, 0)
    //聚合数据
    override def reduce(b: AvgBuffer, a: UserBean): AvgBuffer = {
      b.sum += a.age
      b.count += 1
      b
    }
    //缓冲区合并操作
    override def merge(b1: AvgBuffer, b2: AvgBuffer): AvgBuffer = {
      b1.sum += b2.sum
      b1.count += b2.count
      b1
    }
    //完成最终计算
    override def finish(reduction: AvgBuffer): Double = {
      reduction.sum.toDouble / reduction.count
    }
    override def bufferEncoder: Encoder[AvgBuffer] = Encoders.product
    override def outputEncoder: Encoder[Double] = Encoders.scalaDouble
  }
}

都看到这里了,点赞评论一下吧!!!

点击查看

【Spark】SparkSQL入门解析(二)

你可能感兴趣的:(Spark)