1 概述
本文介绍spark sql的几种执行方式:SQL,DataFramesAPI与Datasets API(但会使用同一个执行引擎),Spark2.0中引入了SparkSession的概念。该篇文章只是做一个简单的了解,让大家有一个感官性的认识。下一篇会对RDD、DataFrame、Dataset进行一个详细的介绍。
spark sql是为了处理结构化数据的一个spark 模块。不同于spark rdd的基本API,Spark SQL中提供的接口将会提供给Spark更多关于结构化数据和计算的信息。在spark内部,sql sql利用这些信息去更好地进行优化。有如下几种方式执行spark sql:SQL,DataFramesAPI与Datasets API。当相同的计算引擎被用来执行一个计算时,有不同的API和语言种类可供选择。这种统一性意味着开发人员可以来回轻松切换各种最熟悉的API来完成同一个计算工作。
2 spark session
Spark2.0中引入了SparkSession的概念,它为用户提供了一个统一的切入点来使用Spark的各项功能,用户不但可以使用DataFrame和Dataset的各种API,学习Spark2的难度也会大大降低。
在2.0版本之前,使用Spark必须先创建SparkConf和SparkContext,代码如下:
//set up the spark configuration and create contexts
val sparkConf = new SparkConf().setAppName("SparkSessionZipsExample").setMaster("local")
// your handle to SparkContext to access other context like SQLContext
val sc = new SparkContext(sparkConf)
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
在Spark2.0中只要创建一个SparkSession就够了,SparkConf、SparkContext和SQLContext都已经被封装在SparkSession当中。下面的代码创建了一个SparkSession对象并设置了一些参数。这里使用了生成器模式,只有此“spark”对象不存在时才会创建一个新对象。
// Create a SparkSession. No need to create SparkContext
// You automatically get it as part of the SparkSession
val warehouseLocation = "file:${system:user.dir}/spark-warehouse"
val spark = SparkSession
.builder()
.appName("Spark SQL basic example")
.config("spark.some.config.option", "some-value")
.enableHiveSupport() //支持hive
.getOrCreate()
执行完上面的代码就可以使用spark对象了。
3 Dataframe
DataFrame是Dataset中一个有名字的列。从概念上,它等价于关系型数据库中的一张表,或者等价于R/Python中的Data Frame,但它在底层做了更好的优化。构造DataFrame的数据源很多:结构化的数据文件、hive表、外部数据库、已经存在的RDD。DataFrame 的API支持java,scal.python,R。
通过SparkSession,应用程序可以从一个现有的RDD、Hive表、Spark数据源来创建一个DataFrame。
首先我们开启一个spark-shell进行测试:
Spark context Web UI available at http://192.168.137.130:4040
Spark context available as 'sc' (master = local[2], app id = local-1524409122149).
Spark session available as 'spark'.
查看启动日志我们会发现上面一句话,sc大家应该不陌生了,这里的spark就是一个SparkSession对象,启动spark-shell默认给我们创建好的。
cat people.json
{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}
读取json文件(这里也体现了可以读取各种数据结构)
val df=spark.read.json("file:///opt/software/spark-2.2.0-bin-2.6.0-cdh5.7.0/examples/src/main/resources/people.json")
这里就会创建一个DataFrame
df.show
这里的show方法,默认只展示20条记录
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+
这里我们可以看一看源码来解读这个show方法,是比较简单的
- show源码解读
def show(): Unit = show(20)
/** 只展示20行
* Displays the top 20 rows of Dataset in a tabular form.
*
* @param truncate Whether truncate long strings. If true, strings more than 20 characters will
* be truncated and all cells will be aligned right
*
* @group action
* @since 1.6.0
*/ 默认为true ,你可以传递一个false可以展示所有行
def show(truncate: Boolean): Unit = show(20, truncate)
df.printSchema()
18/04/22 07:07:22 INFO ContextCleaner: Cleaned accumulator 53
18/04/22 07:07:22 INFO ContextCleaner: Cleaned accumulator 57
18/04/22 07:07:22 INFO ContextCleaner: Cleaned accumulator 55
root
|-- age: long (nullable = true)
|-- name: string (nullable = true)
列出了他的Schema结构(Tree)
df.select("name").show
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
df.select("name","age").show
+-------+----+
| name| age|
+-------+----+
|Michael|null|
| Andy| 30|
| Justin| 19|
+-------+----+
df.select(df.col("name")).show
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
df.select('name).show
(这里要注意,使用这种写法,在控制台是可以直接使用的,
但是在idea中需要导入:import spark.implicits_ 进行隐式转换否则报错。)
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
df.select($"name").show
+-------+
| name|
+-------+
|Michael|
| Andy|
| Justin|
+-------+
df.select($"name",$"age"+10).show
+-------+----------+
| name|(age + 10)|
+-------+----------+
|Michael| null|
| Andy| 40|
| Justin| 29|
+-------+----------+
df.filter($"age">21).show
+---+----+
|age|name|
+---+----+
| 30|Andy|
+---+----+
df.groupBy("age").count().show
+----+-----+
| age|count|
+----+-----+
| 19| 1|
|null| 1|
| 30| 1|
+----+-----+
- select源码解读
/** 可以看到select中可以传递多个列,并且可以对列进行四则运算,默认调用的是col方法,
* 所以这种写法df.select(df.col("name")) 大家应该明白了把。 个人感觉就是各种千奇百怪 的写法都可以哈
* Selects a set of column based expressions.
* {{{
* ds.select($"colA", $"colB" + 1)
* }}}
*
* @group untypedrel
* @since 2.0.0
*/
@scala.annotation.varargs
def select(cols: Column*): DataFrame = withPlan {
Project(cols.map(_.named), logicalPlan)
}
/**
* Selects a set of columns. This is a variant of `select` that can only select
* existing columns using column names (i.e. cannot construct expressions).
*
* {{{
* // The following two are equivalent:
* ds.select("colA", "colB")
* ds.select($"colA", $"colB")
* }}}
*
* @group untypedrel
* @since 2.0.0
*/
@scala.annotation.varargs
def select(col: String, cols: String*): DataFrame = select((col +: cols).map(Column(_)) : _*)
- 使用sql方式生成一个 DataFrame对象
SparkSession中的SQL函数可以让应用程序以编程的方式运行SQL查询语句,让结果返回一个DataFrame。
// Register the DataFrame as a SQL temporary view
df.createOrReplaceTempView("people")
val sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
这种方式创建的是一个临时表只在当前application有作用。
全局临时视图
Spark SQL中的临时视图作用域仅仅在于创建该视图的会话窗口,如果窗口关闭,该视图也终止。
如果你想要一个在所有会话中都生效的临时视图,并且即使应用程序终止该视图仍然存活,你可以创建一个全局临时视图。
全局临时视图与系统保存数据库global_temp相关联,我们必须使用规范的名字来定义它,
比如:SELECT * FROM global_temp.view1.
// Register the DataFrame as a global temporary view
df.createGlobalTempView("people")
// Global temporary view is tied to a system preserved database `global_temp`
spark.sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
// Global temporary view is cross-session
spark.newSession().sql("SELECT * FROM global_temp.people").show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+
个人建议,使用api的方式进行编程,对于一些复杂的sql是很难做出优化的,使用api的方式就会变得简单。
4 Dataset
Dataset是一个分布式数据集合。Dataset是一个在Spark 1.6版本之后才引入的新接口,它既拥有了RDD的优点(强类型、能够使用强大的lambda函数),又拥有Spark SQL的优点(用来一个经过优化的执行引擎)。你可以将一个JVM对象构造成一个Dataset,之后就可以使用一些transformations操作啦。我们可以使用scala,java来访问Dataset API,不支持python哦,当然,由于python的动态特性,很多的Dataset API是可以使用的,R语言也是一样
Dataset有点像RDD,但它并不是使用java或Kryo这样的序列化方式,而是使用专用的编码器将对象进行序列化,以便于在网络上进行处理和传输。虽然编码器和标准的序列化都可以将对象转成字节,但编码器产生动态的代码,它使用的格式允许Spark在不执行反序列化的情况下去执行像过滤、排序、哈希等许许多多的操作。
case class Person(name: String, age: Long)
// Encoders are created for case classes
val caseClassDS = Seq(Person("Andy", 32)).toDS()
caseClassDS.show()
// +----+---+
// |name|age|
// +----+---+
// |Andy| 32|
// +----+---+
// Encoders for most common types are automatically provided by importing spark.implicits._
val primitiveDS = Seq(1, 2, 3).toDS()
primitiveDS.map(_ + 1).collect() // Returns: Array(2, 3, 4)
// DataFrames can be converted to a Dataset by providing a class. Mapping will be done by name
val path = "examples/src/main/resources/people.json"
val peopleDS = spark.read.json(path).as[Person]
peopleDS.show()
// +----+-------+
// | age| name|
// +----+-------+
// |null|Michael|
// | 30| Andy|
// | 19| Justin|
// +----+-------+