来源《Spark快速大数据分析》
1、 结构化数据
Spark SQL 是在 Spark 1.0 中新加入 Spark 的组件,并快速成为了 Spark 中较受欢迎的操作结构化和半结构化数据的方式。结构化数据指的是有结构信息的数据——也就是所有的数据记录都具有一致字段结构的集合。Spark SQL 支持多种结构化数据源作为输入,而且由于 Spark SQL 知道数据的结构信息,它还可以从这些数据源中只读出所需字段。第 9 章将更详细地讲解 Spark SQL,现在我们只展示如何使用它从一些常见数据源中读取数据。
在各种情况下,我们把一条 SQL 查询给 Spark SQL,让它对一个数据源执行查询(选出一些字段或者对字段使用一些函数),然后得到由 Row 对象组成的RDD,每个 Row 对象表示一条记录。在 Java 和 Scala 中, Row 对象的访问是基于下标的。每个 Row 都有一个get() 方法,会返回一个一般类型让我们可以进行类型转换。另外还有针对常见基本类型的 专 用 get() 方 法( 例 如 getFloat() 、 getInt() 、 getLong() 、 getString() 、getShort() 、getBoolean() 等)。在 Python 中,可以使用 row[column_number] 以及 row.column_name 来访问元素。
1.1、 Apache Hive
Apache Hive 是 Hadoop 上的一种常见的结构化数据源。Hive 可以在 HDFS 内或者在其他存储系统上存储多种格式的表。这些格式从普通文本到列式存储格式,应有尽有。Spark SQL 可以读取 Hive 支持的任何表。要把 Spark SQL 连接到已有的 Hive 上,你需要提供 Hive 的配置文件。你需要将 hive-site.xml 文件复制到 Spark 的 ./conf/ 目录下。这样做好之后,再创建出 HiveContext 对象,也就是 Spark SQL 的入口,然后你就可以使用 Hive 查询语言(HQL)来对你的表进行查询,并以由行组成的 RDD 的形式拿到返回数据,如例 5-30 至例 5-32 所示。
例 5-30: 用 Python 创建 HiveContext 并查询数据
from pyspark import SparkConf, SparkContext
from pyspark.sql import HiveContext
#首先创建一个以向量形式存储的RDD.
sc=SparkContext()
hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("SELECT name, age FROM users")
firstRow = rows.first()
print firstRow.name
###1.2、 JSON
如果你有记录间结构一致的 JSON 数据,Spark SQL 也可以自动推断出它们的结构信息,并将这些数据读取为记录,这样就可以使得提取字段的操作变得很简单。要读取 JSON 数据,首先需要和使用 Hive 一样创建一个 HiveContext 。(不过在这种情况下我们不需要安装好 Hive,也就是说你也不需要 hive-site.xml 文件。)然后使用 HiveContext.jsonFile 方法来从整个文件中获取由 Row 对象组成的 RDD。除了使用整个 Row 对象,你也可以将 RDD注册为一张表,然后从中选出特定的字段。例如,假设有一个包含推文的 JSON 文件,格式如例 5-33 所示,每行一条记录。
例 5-34: 在 Python 中使用 Spark SQL 读取 JSON 数据
tweets = hiveCtx.jsonFile("tweets.json")
tweets.registerTempTable("tweets")
results = hiveCtx.sql("SELECT user.name, text FROM tweets")
1.3、基于 RDD
除了读取数据,也可以基于 RDD 创建 SchemaRDD。在 Scala 中,带有 case class 的 RDD可以隐式转换成 SchemaRDD。
在 Python 中,可以创建一个由 Row 对象组成的 RDD,然后调用 inferSchema() ,如例 9-28所示。
例 9-28: 在 Python 中使用 Row 和具名元组创建 SchemaRDD
happyPeopleRDD = sc.parallelize([Row(name="holden", favouriteBeverage="coffee")])
happyPeopleSchemaRDD = hiveCtx.inferSchema(happyPeopleRDD)
happyPeopleSchemaRDD.registerTempTable("happy_people")
1.4、使用 Beeline
在 Beeline 客户端中,你可以使用标准的 HiveQL 命令来创建、列举以及查询数据表。你可以从 Hive 语言手册(https://cwiki.apache.org/confluence/display/Hive/LanguageManual)中找到关于 HiveQL 的所有语法细节,这里只展示一些常见的操作。首先,要从本地数据创建一张数据表,可以使用 CREATE TABLE 命令。然后使用 LOAD DATA命令进行数据读取。Hive 支持读取带有固定分隔符的文本文件,比如 CSV 等格式的文件,如例 9-33 所示。
例 9-33: 读取数据表
CREATE TABLE IF NOT EXISTS mytable (key INT, value STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY ‘,’ ;
LOAD DATA LOCAL INPATH ‘learning-spark-examples/files/int_string.csv’
INTO TABLE mytable;
要 列 举 数 据 表, 可 以 使 用 SHOW TABLES 语 句( 如 例 9-34 所 示 )。 你 也 可 以 通 过 DESCRIBE tableName 查看每张表的结构信息。
例 9-34: 列举数据表
SHOW TABLES;
mytable
Time taken: 0.052 seconds
如果你想要缓存数据表,使用 CACHE TABLE tableName 语句。缓存之后你可以使用 UNCACHE TABLE tableName 命令取消对表的缓存。需要注意的是,之前也提到过,缓存的表会在这个 JDBC 服务器上的所有客户端之间共享。
最后,在 Beeline 中查看查询计划很简单,对查询语句运行 EXPLAIN 即可,如例 9-35 所示。
例 9-35:Spark SQL shell 执行 EXPLAIN
spark-sql> EXPLAIN SELECT * FROM mytable where key = 1;
== Physical Plan ==
Filter (key#16 = 1)
HiveTableScan [key#16,value#17], (MetastoreRelation default, mytable, None), None
Time taken: 0.551 seconds
对于这个查询计划来说,Spark SQL 在一个 HiveTableScan 节点上使用了筛选操作。在这里,你也可以直接写 SQL 语句对数据进行查询。Beeline shell 对于在多用户间共享的缓存数据表上进行快速的数据探索是非常有用的。
2、在应用中使用 Spark SQL
2.1、初始化 Spark SQL
Python 中 SQL 的 import 声明
# 导入Spark SQL
from pyspark.sql import HiveContext, Row
# 当不能引入hive依赖时
from pyspark.sql import SQLContext, Row
添加好 import 声明之后,需要创建出一个 HiveContext 对象。而如果无法引入 Hive 依赖,就创建出一个 SQLContext 对象作为 SQL 的上下文环境(如例 9-6 至例 9-8 所示)。这两个类都需要传入一个 SparkContext 对象作为运行的基础。
在 Python 中创建 SQL 上下文环境
hiveCtx = HiveContext(sc)
有了 HiveContext 或者 SQLContext 之后,我们就可以准备读取数据并进行查询了
2.2、基本查询示例
要在一张数据表上进行查询,需要调用 HiveContext 或 SQLContext 中的 sql() 方法。要做的第一件事就是告诉 Spark SQL 要查询的数据是什么。因此,需要先从 JSON 文件中读取一些推特数据,把这些数据注册为一张临时表并赋予该表一个名字,然后就可以用 SQL 来查询它了。
在 Python 中读取并查询推文
input = hiveCtx.jsonFile(inputFile)
# 注册输入的SchemaRDD
input.registerTempTable("tweets")
# 依据retweetCount (转发计数)选出推文
topTweets = hiveCtx.sql("""SELECT text, retweetCount FROM tweets ORDER BY retweetCount LIMIT 10""")
如果你已经有安装好的 Hive,并且已经把你的 hive-site.xml 文件复制到了$SPARK_HOME/conf 目录下,那么你也可以直接运行 hiveCtx.sql 来查询已有的 Hive 表。
2.3、SchemaRDD
读取数据和执行查询都会返回 SchemaRDD。SchemaRDD 和传统数据库中的表的概念类似。从内部机理来看,SchemaRDD 是一个由 Row 对象组成的 RDD,附带包含每列数据类型的结构信息。 Row 对象只是对基本数据类型(如整型和字符串型等)的数组的封装。我们会在下一部分中进一步探讨 Row 对象的细节。
需要特别注意的是,在今后的 Spark 版本中(1.3 及以后),SchemaRDD 这个名字可能会被改为 DataFrame。这一重命名举动在本书编写完成时仍在讨论中。
SchemaRDD 仍然是 RDD,所以你可以对其应用已有的 RDD 转化操作,比如 map() 和filter() 。然而,SchemaRDD 也提供了一些额外的功能支持。最重要的是,你可以把任意 SchemaRDD 注册为临时表,这样就可以使用 HiveContext.sql 或 SQLContext.sql 来对它进行查询了。你可以通过SchemaRDD 的 registerTempTable() 方法这么做.
临时表是当前使用的 HiveContext 或 SQLContext 中的临时变量,在你的应用退出时这些临时表就不再存在了。
SchemaRDD 可以存储一些基本数据类型,也可以存储由这些类型组成的结构体和数组。SchemaRDD 使用 HiveQL 语(https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL)定义的类型。
SchemaRDD中可以存储的数据类型
|Spark SQL/HiveQL类型 |Scala类型 |Java类型 |Python|
|---- |---- |---- |---- |----|
|TINYINT |Byte|Byte / byte |int / long ( 在 -128 到 127 之间 )|
|SMALLINT |Short |Short / short |int / long ( 在 -32768 到 32767之间 )|
|INT |Int |Int / int |int 或 long|
|BIGINT |Long |Long / long |long|
|FLOAT |Float |Float / float |float|
|DOUBLE| Double |Double / double| float|
|DECIMAL |Scala.math.BigDecimal |java.math.BigDecimal |decimal.Decimal|
|STRING |String |String |string|
|BINARY| Array[Byte] |byte[] |bytearray|
|BOOLEAN |Boolean |Boolean / boolean| bool|
|TIMESTAMP |java.sql.TimeStamp |java.sql.TimeStamp| datetime.datetime|
|ARRAY<DATA_TYPE> |Seq |List |list 、 tuple 或 array|
|MAP<KEY_TYPE, VAL_TYPE> |Map |Map| dict|
|STRUCT<COL1:COL1_TYPE, …>| Row |Row |Row|
最后一种类型,也就是结构体,在 Spark SQL 中直接被表示为其他的 Row 对象。所有这些复杂类型都可以互相嵌套。比如,你可以有结构体组成的数组,或包含结构体的映射表。
使用 Row 对象
Row 对象表示 SchemaRDD 中的记录,其本质就是一个定长的字段数组。在 Scala/Java 中,Row 对象有一系列 getter 方法,可以通过下标获取每个字段的值。标准的取值方法 get (或Scala 中的 apply ),读入一个列的序号然后返回一个 Object 类型(或 Scala 中的 Any 类型)的对象,然后由我们把对象转为正确的类型。对于 Boolean 、 Byte 、 Double 、 Float 、 Int 、Long 、 Short 和 String 类型,都有对应的 getType() 方法,可以把值直接作为相应的类型返回。例如, getString(0) 会把字段 0 的值作为字符串返回,如例 9-12 和例 9-13 所示。
例 9-12: 在 Scala 中访问 topTweet 这个 SchemaRDD 中的 text 列(也就是第一列)
val topTweetText = topTweets.map(row => row.getString(0))
例 9-13: 在 Java 中访问 topTweet 这个 SchemaRDD 中的 text 列(也就是第一列)
JavaRDD<String> topTweetText = topTweets.toJavaRDD().map(new Function<Row, String>() {
public String call(Row row) {
return row.getString(0);
}});
在 Python 中,由于没有显式的类型系统, Row 对象变得稍有不同。我们使用 row[i] 来访问第 i 个元素。除此之外,Python 中的 Row 还支持以 row .column_name 的形式使用名字来访问其中的字段,如例 9-14 所示。如果你不确定具体的列名,我们会在 9.3.3 节中讲到如何输出结构信息。
例 9-14: 在 Python 中访问 topTweet 这个 SchemaRDD 中的 text 列
topTweetText = topTweets.map(lambda row: row.text)
2.4、缓存
Spark SQL 的缓存机制与 Spark 中的稍有不同。由于我们知道每个列的类型信息,所以Spark 可以更加高效地存储数据。为了确保使用更节约内存的表示方式进行缓存而不是储存整个对象,应当使用专门的 hiveCtx.cacheTable(“tableName”) 方法。当缓存数据表时,Spark SQL 使用一种列式存储格式在内存中表示数据。这些缓存下来的表只会在驱动器程序的生命周期里保留在内存中,所以如果驱动器进程退出,就需要重新缓存数据。和缓存RDD 时的动机一样,如果想在同样的数据上多次运行任务或查询时,就应把这些数据表缓存起来。
在 Spark 1.2 中,RDD 上原有的 cache() 方法也会引发一次对 cacheTable()方法的调用。你也可以使用 HiveQL/SQL 语句来缓存表。只需要运行 CACHE TABLE tableName 或 UNCACHETABLE tableName 来缓存表或者删除已有的缓存即可。这种使用方式在 JDBC 服务器的命令行客户端中很常用。被缓存的 SchemaRDD 以与其他 RDD 相似的方式在 Spark 的应用用户界面中呈现