大数据系列之SparkSql应用解析(三)

文章目录

  • 第1章 Spark SQL概述
    • 1.1 什么是Spark SQL
    • 1.2 RDD vs DataFrames vs DataSet
      • 1.2.1 RDD
      • 1.2.2 Dataframe
      • 1.2.3 Dataset
      • 1.2.4 三者的共性
      • 1.2.5 三者的区别
  • 第2章 执行SparkSQL查询
    • 2.1 命令行查询流程
    • 2.2 IDEA创建SparkSQL程序
  • 第3章 SparkSQL解析
    • 3.1 新的起始点SparkSession
    • 3.2 创建DataFrames
    • 3.3 DataFrame常用操作
      • 3.3.1 DSL风格语法
      • 3.3.2 SQL风格语法
    • 3.4 创建DataSet
    • 3.5 Dataset和RDD互操作
      • 3.5.1 通过反射获取Scheam
      • 3.5.2 通过编程设置Schema(StructType)
    • 3.6 类型之间的转换总结
    • 3.7 用户自定义函数
      • 3.7.1 用户自定义UDF函数
      • 3.7.2 用户自定义聚合函数
        • 3.7.2.1 弱类型用户自定义聚合函数
        • 3.7.2.2 强类型用户自定义聚合函数
  • 第4章 SparkSQL数据源
    • 4.1 通用加载/保存方法
      • 4.1.1 手动指定选项
      • 4.1.2 文件保存选项
    • 4.2 Parquet文件
      • 4.2.1 Parquet读写
      • 4.2.2 Schema合并
    • 4.3 Hive数据库
      • 4.3.1 内嵌Hive应用
      • 4.3.2 外部Hive应用
    • 4.4 JSON数据集
    • 4.5 JDBC
  • 第5章 JDBC/ODBC服务器
  • 第6章 运行Spark SQL CLI

第1章 Spark SQL概述

1.1 什么是Spark SQL

大数据系列之SparkSql应用解析(三)_第1张图片
Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用。Hive是将Hive SQL转换成MapReduce然后提交到集群上执行,大大简化了编写MapReduce的程序的复杂性,由于MapReduce这种计算模型执行效率比较慢。所有Spark SQL的应运而生,它是将Spark SQL转换成RDD,然后提交到集群执行,执行效率非常快!
1.易整合
大数据系列之SparkSql应用解析(三)_第2张图片
2.统一的数据访问方式
大数据系列之SparkSql应用解析(三)_第3张图片
3.兼容Hive
大数据系列之SparkSql应用解析(三)_第4张图片
4.标准的数据连接
大数据系列之SparkSql应用解析(三)_第5张图片
大数据系列之SparkSql应用解析(三)_第6张图片SparkSQL可以看做是一个转换层,向下对接各种不同的结构化数据源,向上提供不同的数据访问方式。

1.2 RDD vs DataFrames vs DataSet

大数据系列之SparkSql应用解析(三)_第7张图片在SparkSQL中Spark为我们提供了两个新的抽象,分别是DataFrame和DataSet。他们和RDD有什么区别呢?首先从版本的产生上来看:
RDD (Spark1.0) —> Dataframe(Spark1.3) —> Dataset(Spark1.6)
如果同样的数据都给到这三个数据结构,他们分别计算之后,都会给出相同的结果。不同是的他们的执行效率和执行方式。
在后期的Spark版本中,DataSet会逐步取代RDD和DataFrame成为唯一的API接口。

1.2.1 RDD

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

1.2.2 Dataframe

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

定制化内存管理
数据以二进制的方式存在于非堆内存,节省了大量空间之外,还摆脱了GC的限制。
大数据系列之SparkSql应用解析(三)_第9张图片优化的执行计划
查询计划通过Spark catalyst optimiser进行优化。
大数据系列之SparkSql应用解析(三)_第10张图片
比如下面一个例子:

User.join(Events,User("id") === Events("uid"))
.filter(Events("sysDate") >"2020-08-10")

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

1.2.3 Dataset

  1. 是Dataframe API的一个扩展,是Spark最新的数据抽象。

  2. 用户友好的API风格,既具有类型安全检查也具有Dataframe的查询优化特性。

  3. Dataset支持编解码器,当需要访问非堆上的数据时可以避免反序列化整个对象,提高了效率。

  4. 样例类被用来在Dataset中定义数据的结构信息,样例类中每个属性的名称直接映射到DataSet中的字段名称。

  5. Dataframe是Dataset的特列,DataFrame=Dataset[Row] ,所以可以通过as方法将Dataframe转换为Dataset。Row是一个类型,跟Car、Person这些的类型一样,所有的表结构信息我都用Row来表示。

  6. DataSet是强类型的。比如可以有Dataset[Car],Dataset[Person].

DataFrame只是知道字段,但是不知道字段的类型,所以在执行这些操作的时候是没办法在编译的时候检查是否类型失败的,比如你可以对一个String进行减法操作,在执行的时候才报错,而DataSet不仅仅知道字段,而且知道字段类型,所以有更严格的错误检查。就跟JSON对象和类对象之间的类比。

RDD让我们能够决定怎么做,而DataFrame和DataSet让我们决定做什么,控制的粒度不一样。
大数据系列之SparkSql应用解析(三)_第12张图片

1.2.4 三者的共性

1、RDD、DataFrame、Dataset全都是spark平台下的分布式弹性数据集,为处理超大型数据提供便利。
2、三者都有惰性机制,在进行创建、转换,如map方法时,不会立即执行,只有在遇到Action如foreach时,三者才会开始遍历运算,极端情况下,如果代码里面有创建、转换,但是后面没有在Action中使用对应的结果,在执行时会被直接跳过。

val sparkconf = new SparkConf().setMaster("local").setAppName("test").set("spark.port.maxRetries","1000")
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进行操作许多操作都需要这个包进行支持 import spark.implicits._。

7、DataFrame和Dataset均可使用模式匹配获取各个字段的值和类型

​ DataFrame:

​ testDF.map{ case Row(col1:String,col2:Int)=> println(col1);println(col2) col1 case _=> “” }

Dataset:

​ case class Coltest(col1:String,col2:Int)extends Serializable //定义字段名和类型 testDS.map{ case Coltest(col1:String,col2:Int)=> println(col1);println(col2) col1 case _=> “” }

1.2.5 三者的区别

RDD:

1、RDD一般和spark mlib同时使用。

2、RDD不支持sparksql操作。

DataFrame:

1、与RDD和Dataset不同,DataFrame每一行的类型固定为Row,只有通过解析才能获取各个字段的值,每一列的值没法直接访问。

2、DataFrame与Dataset一般与spark ml同时使用。

3、DataFrame与Dataset均支持sparksql的操作,比如select,groupby之类,还能注册临时表/视窗,进行sql语句操作。

4、DataFrame与Dataset支持一些特别方便的保存方式,比如保存成csv,可以带上表头,这样每一列的字段名一目了然利用这样的保存方式,可以方便的获得字段名和列的对应,而且分隔符(delimiter)可以自由指定。

Dataset:

Dataset和DataFrame拥有完全相同的成员函数,区别只是每一行的数据类型不同。

DataFrame也可以叫Dataset[Row],每一行的类型是Row,不解析,每一行究竟有哪些字段,各个字段又是什么类型都无从得知,只能用上面提到的getAS方法或者共性中的第七条提到的模式匹配拿出特定字段而Dataset中,每一行是什么类型是不一定的,在自定义了case class之后可以很自由的获得每一行的信息。

可以看出,Dataset在需要访问列中的某个字段时是非常方便的,然而,如果要写一些适配性很强的函数时,如果使用Dataset,行的类型又不确定,可能是各种case class,无法实现适配,这时候用DataFrame即Dataset[Row]就能比较好的解决问题

第2章 执行SparkSQL查询

2.1 命令行查询流程

打开Spark shell

例子:查询大于18岁的用户

val df = spark.read.json("file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/people.json")
df.show
df.filter($"age">18).show
df.createOrReplaceTempView("people")
spark.sql("select * from people where age >18").show

大数据系列之SparkSql应用解析(三)_第13张图片

2.2 IDEA创建SparkSQL程序

IDEA中程序的打包和运行方式都和SparkCore类似,Maven依赖中需要添加新的依赖项:

<dependency>
    <groupId>org.apache.sparkgroupId>
    <artifactId>spark-sql_2.11artifactId>
    <version>2.3.4version>
dependency>

程序如下:

import org.apache.spark.sql.SparkSession
/**
  * sparkSQL入门
  */
object FirstSql {
  def main(args: Array[String]) {
    //创建SparkConf()并设置App名称
    val spark = SparkSession
      .builder()
      .appName("FirstSql")
      .getOrCreate()
    // 通过引入隐式转换可以将RDD的操作添加到 DataFrame上。
    import spark.implicits._
    //通过spark.read操作读取JSON数据。
    val path = this.getClass.getClassLoader.getResource("people.json").getPath
    val df = spark.read.json(path)
    //show操作类似于Action,将DataFrame直接打印到Console。
    df.show()
    // DSL风格的使用方式中,属性的获取方法
    df.filter($"age" > 18).show()
    // 将DataFrame注册为一张临时表
    df.createOrReplaceTempView("person")
    // 通过spark.sql方法来运行正常的SQL语句。
    spark.sql("SELECT * FROM person where age > 18").show()
    // 关闭整个SparkSession。
    spark.stop()
  }
}

第3章 SparkSQL解析

3.1 新的起始点SparkSession

在老的版本中,SparkSQL提供两种SQL查询起始点,一个叫SQLContext,用于Spark自己提供的SQL查询,一个叫HiveContext,用于连接Hive的查询,SparkSession是Spark最新的SQL查询起始点,实质上是SQLContext和HiveContext的组合,所以在SQLContext和HiveContext上可用的API在SparkSession上同样是可以使用的。SparkSession内部封装了sparkContext,所以计算实际上是由sparkContext完成的。

val spark = SparkSession
.builder()
.appName("FirstSql")
.getOrCreate()
import spark.implicits._

SparkSession.builder 用于创建一个SparkSession。import spark.implicits._的引入是用于将DataFrames隐式转换成RDD,使df能够使用RDD中的方法。

如果需要Hive支持,则需要以下创建语句:

val spark = SparkSession
.builder()
.appName("FirstSql")
.enableHiveSupport()
.getOrCreate()

3.2 创建DataFrames

在Spark SQL中SparkSession是创建DataFrames和执行SQL的入口,创建DataFrames有三种方式,一种是可以从一个存在的RDD进行转换,还可以从Hive Table进行查询返回,或者通过Spark的数据源进行创建。从Spark数据源进行创建:
大数据系列之SparkSql应用解析(三)_第14张图片从RDD进行转换:
大数据系列之SparkSql应用解析(三)_第15张图片

3.3 DataFrame常用操作

3.3.1 DSL风格语法

//查询表中姓名,每个人年龄+1
df.select($"name", $"age" + 1).show()
//查询年龄大于18的人
df.filter($"age" > 18).show()
//按照年龄分组统计人数
df.groupBy("age").count().show()

3.3.2 SQL风格语法

//注册一张临时表
df.createOrReplaceTempView("people")
spark.sql("SELECT * FROM people").show
//注册一张全局表
df.createGlobalTempView("people")
spark.sql("SELECT * FROM global_temp.people").show()

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

3.4 创建DataSet

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

//定义样例类
case class Person(name: String, age: Long)
val caseClassDS = Seq(Person("Tom", 18)).toDS()
caseClassDS.show()

val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()

3.5 Dataset和RDD互操作

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

3.5.1 通过反射获取Scheam

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

3.5.2 通过编程设置Schema(StructType)

如果case类不能够提前定义,可以通过下面三个步骤定义一个DataFrame

  1. 创建一个多行结构的RDD;
  2. 创建用StructType来表示的行结构信息。
  3. 通过SparkSession提供的createDataFrame方法来应用Schema .
import org.apache.spark.sql.types._
val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/people.txt"
val peopleRDD = spark.sparkContext.textFile(path)
val schemaString = "name age"
val fields = schemaString.split(" ").map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields)
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")
spark.sql("SELECT name ,age FROM people").show

3.6 类型之间的转换总结

RDD、DataFrame、Dataset三者有许多共性,有各自适用的场景常常需要在三者之间转换

DataFrame/Dataset转RDD:

val rdd1 = peopleDF.rdd
val rdd2 = peopleDS.rdd

RDD转DataFrame:

import spark.implicits._
val peopleDF = rdd.map {line=> (line._1,line._2)  }.toDF("name","age")

RDD转Dataset:

case class Person(name: String, age: Long)
val peopleDS = rdd.map(_.split(",")).map(attributes => Person(attributes(0), attributes(1).trim)).toDS

Dataset转DataFrame:

import spark.implicits._
val peopleDF = peopleDS.toDF

DataFrame转Dataset:

import spark.implicits._
case class Person(name: String, age: Long) extends Serializable //定义字段名和类型
val peopleDS = peopleDF.as[Person]

3.7 用户自定义函数

通过spark.udf功能用户可以自定义函数。

3.7.1 用户自定义UDF函数

val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path)
spark.udf.register("nameUpper", (x:String)=>x.toUpperCase) //将名字转换成大写
peopleDS.createOrReplaceTempView("people")
spark.sql("Select nameUpper(name) name, age from people").show()

3.7.2 用户自定义聚合函数

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

3.7.2.1 弱类型用户自定义聚合函数

通过继承UserDefinedAggregateFunction来实现用户自定义聚合函数。下面展示一个求平均工资的自定义聚合函数。

import org.apache.spark.sql.expressions.MutableAggregationBuffer
import org.apache.spark.sql.expressions.UserDefinedAggregateFunction
import org.apache.spark.sql.types._
import org.apache.spark.sql.Row
import org.apache.spark.sql.SparkSession
object MyAverage extends UserDefinedAggregateFunction {
    // 聚合函数输入参数的数据类型 
    def inputSchema: StructType = StructType(StructField("inputColumn", LongType) :: Nil)
    // 聚合缓冲区中值得数据类型 
    def bufferSchema: StructType = {
        StructType(StructField("sum", LongType) :: StructField("count", LongType) :: Nil)
    }
    // 返回值的数据类型 
    def dataType: DataType = DoubleType
    // 对于相同的输入是否一直返回相同的输出。 
    def deterministic: Boolean = true
    // 初始化
    def initialize(buffer: MutableAggregationBuffer): Unit = {
    // 存工资的总额
    buffer(0) = 0L
    // 存工资的个数
    buffer(1) = 0L
    }
    // 相同Execute间的数据合并。 
    def update(buffer: MutableAggregationBuffer, input: Row): Unit = {
    if (!input.isNullAt(0)) {
    buffer(0) = buffer.getLong(0) + input.getLong(0)
    buffer(1) = buffer.getLong(1) + 1
    }
    }
    // 不同Execute间的数据合并 
    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)
}


//测试代码
val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/employees.json"
val df = spark.read.json(path)
df.createOrReplaceTempView("employees")
spark.udf.register("myAverage", MyAverage)// 注册函数
spark.sql("SELECT myAverage(salary) as average_salary FROM employees").show()


3.7.2.2 强类型用户自定义聚合函数

通过继承Aggregator来实现强类型自定义聚合函数,同样是求平均工资

import org.apache.spark.sql.expressions.Aggregator
import org.apache.spark.sql.Encoder
import org.apache.spark.sql.Encoders
import org.apache.spark.sql.SparkSession

// 既然是强类型,可能有case类
case class Employee(name: String, salary: Long)
case class Average(var sum: Long, var count: Long)

object MyAverage extends Aggregator[Employee, Average, Double] {
    // 定义一个数据结构,保存工资总数和工资总个数,初始都为0
    def zero: Average = Average(0L, 0L)
    // Combine two values to produce a new value. For performance, the function may modify `buffer`
    // and return it instead of constructing a new object
    def reduce(buffer: Average, employee: Employee): Average = {
    buffer.sum += employee.salary
    buffer.count += 1
    buffer
    }
    // 聚合不同execute的结果
    def merge(b1: Average, b2: Average): Average = {
    b1.sum += b2.sum
    b1.count += b2.count
    b1
    }
    // 计算输出
    def finish(reduction: Average): Double = reduction.sum.toDouble / reduction.count
    // 设定之间值类型的编码器,要转换成case类
    // Encoders.product是进行scala元组和case类转换的编码器 
    def bufferEncoder: Encoder[Average] = Encoders.product
    // 设定最终输出值的编码器
    def outputEncoder: Encoder[Double] = Encoders.scalaDouble
}

//测试代码
val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/employees.json"
val ds = spark.read.json(path).as[Employee]
val averageSalary = MyAverage.toColumn.name("average_salary")
ds.select(averageSalary).show()

第4章 SparkSQL数据源

4.1 通用加载/保存方法

4.1.1 手动指定选项

Spark SQL的DataFrame接口支持多种数据源的操作。一个DataFrame可以进行RDDs方式的操作,也可以被注册为临时表。把DataFrame注册为临时表之后,就可以对该DataFrame执行SQL查询。

Spark SQL的默认数据源为Parquet格式。数据源为Parquet文件时,Spark SQL可以方便的执行所有的操作。修改配置项spark.sql.sources.default,可修改默认数据源格式。

​ val df = sqlContext.read.load(“examples/src/main/resources/users.parquet”)

​ df.select(“name”, “favorite_color”).write.save(“namesAndFavColors.parquet”)

当数据源格式不是parquet格式文件时,需要手动指定数据源的格式。数据源格式需要指定全名(例如:org.apache.spark.sql.parquet),如果数据源格式为内置格式,则只需要指定简称定json, parquet, jdbc, orc, libsvm, csv, text来指定数据的格式。

可以通过SparkSession提供的read.load方法用于通用加载数据,使用write和save保存数据。

​ val peopleDF = spark.read.format(“json”).load(“examples/src/main/resources/people.json”)

​ peopleDF.write.format(“parquet”).save(“hdfs://master01:9000/namesAndAges.parquet”)

除此之外,可以直接运行SQL在文件上:

​ val sqlDF = spark.sql(“SELECT * FROM parquet.hdfs://master01:9000/namesAndAges.parquet”)

​ sqlDF.show()

4.1.2 文件保存选项

可以采用SaveMode执行存储操作,SaveMode定义了对数据的处理模式。需要注意的是,这些保存模式不使用任何锁定,不是原子操作。此外,当使用Overwrite方式执行时,在输出新数据之前原数据就已经被删除。SaveMode详细介绍如下表:

Scala/Java Any Language Meaning
SaveMode.ErrorIfExists(default) error(default) 如果文件存在,则报错
SaveMode.Append append 追加
SaveMode.Overwrite overwrite 覆写
SaveMode.Ignore ignore 数据存在,则忽略

4.2 Parquet文件

Parquet是一种流行的列式存储格式,可以高效地存储具有嵌套字段的记录。
大数据系列之SparkSql应用解析(三)_第16张图片

4.2.1 Parquet读写

Parquet格式经常在Hadoop生态圈中被使用,它也支持Spark SQL的全部数据类型。Spark SQL 提供了直接读取和存储 Parquet 格式文件的方法。

import spark.implicits._
val path = "file:///opt/apache/spark-2.3.4-bin-hadoop2.7/examples/src/main/resources/people.json"
val peopleDF = spark.read.json(path)
peopleDF.write.parquet("hdfs://master01:9000/people.parquet")
val parquetFileDF = spark.read.parquet("hdfs://master01:9000/people.parquet")
parquetFileDF.createOrReplaceTempView("parquetFile")
spark.sql("SELECT name FROM parquetFile WHERE age>18").show

4.2.2 Schema合并

像ProtocolBuffer、Avro和Thrift那样,Parquet也支持Schema evolution(Schema演变)。用户可以先定义一个简单的Schema,然后逐渐的向Schema中增加列描述。通过这种方式,用户可以获取多个有不同Schema但相互兼容的Parquet文件。现在Parquet数据源能自动检测这种情况,并合并这些文件的schemas。因为Schema合并是一个高消耗的操作,在大多数情况下并不需要,所以Spark SQL从1.5.0开始默认关闭了该功能。可以通过下面两种方式开启该功能:当数据源为Parquet文件时,将数据源选项mergeSchema设置为true设置全局SQL选项spark.sql.parquet.mergeSchema为true.

4.3 Hive数据库

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,否则就是本地文件系统)。

4.3.1 内嵌Hive应用

如果要使用内嵌的Hive,什么都不用做,直接用就可以了。
大数据系列之SparkSql应用解析(三)_第17张图片

4.3.2 外部Hive应用

如果想连接外部已经部署好的Hive,需要通过以下几个步骤。

  1. 将Hive中的hive-site.xml拷贝或者软连接到Spark安装目录下的conf目录下。
  2. 打开spark shell,注意带上访问Hive元数据库的JDBC客户端

4.4 JSON数据集

Spark SQL 能够自动推测 JSON数据集的结构,并将它加载为一个Dataset[Row]. 可以通过SparkSession.read.json()去加载一个 Dataset[String]或者一个JSON 文件.注意,这个JSON文件不是一个传统的JSON文件,每一行都得是一个JSON串。

4.5 JDBC

Spark SQL可以通过JDBC从关系型数据库中读取数据的方式创建DataFrame,通过对DataFrame一系列的计算后,还可以将数据再写回关系型数据库中。

注意,需要将相关的数据库驱动放到spark的类路径下。

//java版本
Dataset<Row> dataDS = spark.read().jdbc(JdbcUtils.UGCURL, "tableName", JdbcUtils.getProJdbcInfo());
dataDS.createOrReplaceTempView("tableName");
String sql= "";
spark.sql(sql).write().mode(SaveMode.Overwrite).jdbc(JdbcUtils.URL, "tableName", JdbcUtils.getProJdbcInfo());

//scala版本
val connectionProperties = new Properties()
connectionProperties.put("user", "root")
connectionProperties.put("password", "123456")
val dataDS = spark.read.jdbc("jdbc:mysql://localhost:3306/mysql", "db", connectionProperties)
val sql = ""    
spark.sql(sql).write().mode(SaveMode.Overwrite).jdbc(JdbcUtils.URL, "tableName", JdbcUtils.getProJdbcInfo());    

第5章 JDBC/ODBC服务器

Spark SQL也提供JDBC连接支持,这对于让商业智能(BI)工具连接到Spark集群上以 及在多用户间共享一个集群的场景都非常有用。JDBC 服务器作为一个独立的 Spark 驱动 器程序运行,可以在多用户之间共享。任意一个客户端都可以在内存中缓存数据表,对表 进行查询。集群的资源以及缓存数据都在所有用户之间共享。 Spark SQL的JDBC服务器与Hive中的HiveServer2相一致。由于使用了Thrift通信协议,它也被称为“Thrift server”。 服务器可以通过 Spark 目录中的 sbin/start-thriftserver.sh 启动。这个 脚本接受的参数选项大多与 spark-submit 相同。默认情况下,服务器会在 localhost:10000 上进行监听,我们可以通过环境变量(HIVE_SERVER2_THRIFT_PORT 和 HIVE_SERVER2_THRIFT_BIND_HOST)修改这些设置,也可以通过 Hive配置选项(hive. server2.thrift.port 和 hive.server2.thrift.bind.host)来修改。你也可以通过命令行参 数–hiveconf property=value来设置Hive选项。

./sbin/start-thriftserver.sh \
--hiveconf hive.server2.thrift.port=<listening-port> \
--hiveconf hive.server2.thrift.bind.host=<listening-host> \
--master <master-uri>

第6章 运行Spark SQL CLI

Spark SQL CLI可以很方便的在本地运行Hive元数据服务以及从命令行执行查询任务。需要注意的是,Spark SQL CLI不能与Thrift JDBC服务交互。
在Spark目录下执行如下命令启动Spark SQL CLI:./bin/spark-sql配置Hive需要替换 conf/ 下的 hive-site.xml 。

你可能感兴趣的:(大数据系列学习笔记)