Spark SQL

本文介绍Spark 用来操作结构化和半结构化数据的接口——Spark SQL。结构化数据是指任 何有结构信息的数据。所谓结构信息,就是每条记录共用的已知的字段集合。当数据符合 这样的条件时,Spark SQL 就会使得针对这些数据的读取和查询变得更加简单高效。具体 来说,Spark SQL 提供了以下三大功能(见图 9-1)。

(1) Spark SQL 可以从各种结构化数据源(例如 JSON、Hive、Parquet 等)中读取数据。

(2) Spark SQL 不仅支持在 Spark 程序内使用 SQL 语句进行数据查询,也支持从类似商业 智能软件 Tableau 这样的外部工具中通过标准数据库连接器(JDBC/ODBC)连接 Spark SQL 进行查询。

(3) 当在 Spark 程序内使用 Spark SQL 时,Spark SQL 支持 SQL 与常规的 Python/Java/Scala 代码高度整合,包括连接 RDD 与 SQL 表、公开的自定义 SQL 函数接口等。这样一来, 许多工作都更容易实现了

为了实现这些功能,Spark SQL 提供了一种特殊的 RDD,叫作 SchemaRDD。1SchemaRDD 是存放 Row 对象的 RDD,每个 Row 对象代表一行记录。SchemaRDD 还包含记录的结构信 息(即数据字段)。SchemaRDD 看起来和普通的 RDD 很像,但是在内部,SchemaRDD 可 以利用结构信息更加高效地存储数据。此外,SchemaRDD 还支持 RDD 上所没有的一些新 操作,比如运行 SQL 查询。SchemaRDD 可以从外部数据源创建,也可以从查询结果或普 通 RDD 中创建。

注 1:1.3.0 及后续版本中,SchemaRDD 已经被 DataFrame 所取代。

image.png

下面会先讲解如何在常规 Spark 程序中使用 SchemaRDD,以读取和查询结构化数据。接下 来会讲解 Spark SQL 的 JDBC 服务器,它可以让你在一个共享的服务器上运行 Spark SQL, 也可以让 SQL shell 或者类似 Tableau 的可视化工具连接它而使用。最后会讨论更多高级特 性。Spark SQL 是 Spark 中比较新的组件,在 Spark 1.3 以及后续版本中还会有重大升级。

9.1 连接Spark SQL

跟 Spark 的其他程序库一样,要在应用中引入 Spark SQL 需要添加一些额外的依赖。这种分离机制使得 Spark 内核的编译无需依赖大量额外的包。

Apache Hive 是 Hadoop 上的 SQL 引擎,Spark SQL 编译时可以包含 Hive 支持,也可以 不包含。包含 Hive 支持的 Spark SQL 可以支持 Hive 表访问、UDF(用户自定义函数)、 SerDe(序列化格式和反序列化格式),以及 Hive 查询语言(HiveQL/HQL)。需要强调的 一点是,如果要在 Spark SQL 中包含 Hive 的库,并不需要事先安装 Hive。一般来说,最 好还是在编译 Spark SQL 时引入 Hive 支持,这样就可以使用这些特性了。如果你下载的 是二进制版本的 Spark,它应该已经在编译时添加了 Hive 支持。而如果你是从代码编译 Spark,你应该使用 sbt/sbt -Phive assembly 编译,以打开 Hive 支持。

当使用 Spark SQL 进行编程时,根据是否使用 Hive 支持,有两个不同的入口。推荐使用 的入口是 HiveContext,它可以提供 HiveQL 以及其他依赖于 Hive 的功能的支持。更为基 础的 SQLContext 则支持 Spark SQL 功能的一个子集,子集中去掉了需要依赖于 Hive 的功 能。这种分离主要是为那些可能会因为引入 Hive 的全部依赖而陷入依赖冲突的用户而设 计的。使用 HiveContext 不需要事先部署好 Hive。

我们推荐使用 HiveQL 作为 Spark SQL 的查询语言。在 Spark1.0 和 1.1 中,Spark SQL 是 基 于 Hive 0.12 的, 而 在 Spark 1.2 中,Spark SQL 支 持 Hive 0.13。如果你了解标准 SQL,应该也会对 HiveQL 非常熟悉。

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

9.2 在应用中使用Spark SQL

Spark SQL 最强大之处就是可以在 Spark 应用内使用。这种方式让我们可以轻松读取数据并使用 SQL 查询,同时还能把这一过程和普通的 Python/Java/Scala 程序代码结合在一起。

要以这种方式使用 Spark SQL,需要基于已有的 SparkContext 创建出一个 HiveContext(如果 使用的是去除了 Hive 支持的 Spark 版本,则创建出 SQLContext)。这个上下文环境提供了对 Spark SQL 的数据进行查询和交互的额外函数。使用 HiveContext 可以创建出表示结构化数据 的 SchemaRDD,并且使用 SQL 或是类似 map() 的普通 RDD 操作来操作这些 SchemaRDD。

9.2.1 初始化Spark SQL

要开始使用 Spark SQL,首先要在程序中添加一些 import 声明,如例 9-2 所示。

例 9-2:Scala 中 SQL 的 import 声明
// 导入Spark SQL
import org.apache.spark.sql.hive.HiveContext // 如果不能使用hive依赖的话
import org.apache.spark.sql.SQLContext
例 9-4:Java 中 SQL 的 import 声明
// 导入Spark SQL
import org.apache.spark.sql.hive.HiveContext; // 当不能使用hive依赖时
import org.apache.spark.sql.SQLContext;
// 导入JavaSchemaRDD
import org.apache.spark.sql.SchemaRDD;
import org.apache.spark.sql.Row;

添加好 import 声明之后,需要创建出一个 HiveContext 对象。而如果无法引入 Hive 依赖, 就创建出一个 SQLContext 对象作为 SQL 的上下文环境(如例 9-6 至例 9-8 所示)。这两个 类都需要传入一个 SparkContext 对象作为运行的基础。

例 9-6:在 Scala 中创建 SQL 上下文环境 val sc = new SparkContext(...)
     val hiveCtx = new HiveContext(sc)
例 9-7:在 Java 中创建 SQL 上下文环境 JavaSparkContext ctx = new JavaSparkContext(...);
     SQLContext sqlCtx = new HiveContext(ctx);
例 9-8:在 Python 中创建 SQL 上下文环境 hiveCtx = HiveContext(sc)

有了 HiveContext 或者 SQLContext 之后,我们就可以准备读取数据并进行查询了。

9.2.2 基本查询示例

要在一张数据表上进行查询,需要调用 HiveContext 或 SQLContext 中的 sql() 方法。要做 的第一件事就是告诉 Spark SQL 要查询的数据是什么。因此,需要先从 JSON 文件中读取 一些推特数据,把这些数据注册为一张临时表并赋予该表一个名字,然后就可以用 SQL 来 查询它了。(9.3 节会更深入地讲解读取数据的细节。)接下来,就可以根据 retweetCount 字段(转发计数)选出最热门的推文,如例 9-9 至例 9-11 所示。

例 9-9:在 Scala 中读取并查询推文
val input = hiveCtx.jsonFile(inputFile)
// 注册输入的SchemaRDD
input.registerTempTable("tweets")
// 依据retweetCount(转发计数)选出推文
val topTweets = hiveCtx.sql("SELECT text, retweetCount FROM
       tweets ORDER BY retweetCount LIMIT 10")

例 9-10:在 Java 中读取并查询推文
SchemaRDD input = hiveCtx.jsonFile(inputFile);
// 注册输入的SchemaRDD
input.registerTempTable("tweets");
// 依据retweetCount(转发计数)选出推文
SchemaRDD topTweets = hiveCtx.sql("SELECT text, retweetCount FROM
       tweets ORDER BY retweetCount LIMIT 10");

例 9-11:在 Python 中读取并查询推文
input = hiveCtx.jsonFile(inputFile)
# 注册输入的SchemaRDD
input.registerTempTable("tweets")
# 依据retweetCount(转发计数)选出推文
topTweets = hiveCtx.sql("""SELECT text, retweetCount FROM
       tweets ORDER BY retweetCount LIMIT 10""")

9.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 语法


image.png

image.png

最后一种类型,也就是结构体,在 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 topTweetText = topTweets.toJavaRDD().map(new Function() { public String call(Row row) {
           return row.getString(0);
         }});

9.2.4 缓存

Spark SQL 的缓存机制与 Spark 中的稍有不同。由于我们知道每个列的类型信息,所以 Spark 可以更加高效地存储数据。为了确保使用更节约内存的表示方式进行缓存而不是储 存整个对象,应当使用专门的 hiveCtx.cacheTable("tableName") 方法。当缓存数据表时, Spark SQL 使用一种列式存储格式在内存中表示数据。这些缓存下来的表只会在驱动器程 序的生命周期里保留在内存中,所以如果驱动器进程退出,就需要重新缓存数据。和缓存 RDD 时的动机一样,如果想在同样的数据上多次运行任务或查询时,就应把这些数据表缓 存起来。

你也可以使用 HiveQL/SQL 语句来缓存表。只需要运行 CACHE TABLEtableName 或 UNCACHE TABLEtableName 来缓存表或者删除已有的缓存即可。这种使用方式在 JDBC 服务器的命令 行客户端中很常用。

9.3 读取和存储数据

Spark SQL 支持很多种结构化数据源,可以让你跳过复杂的读取过程,轻松从各种数据 源中读取到 Row 对象。这些数据源包括 Hive 表、JSON 和 Parquet 文件。此外,当你使用 SQL 查询这些数据源中的数据并且只用到了一部分字段时,Spark SQL 可以智能地只扫描 这些用到的字段,而不是像 SparkContext.hadoopFile 中那样简单粗暴地扫描全部数据。

除这些数据源之外,你也可以在程序中通过指定结构信息,将常规的 RDD 转化为 SchemaRDD。这使得在 Python 或者 Java 对象上运行 SQL 查询更加简单。当需要计算许 多数值时,SQL 查询往往更加简洁(比如要同时求出平均年龄、最大年龄、不重复的用 户 ID 数目等)。不仅如此,你还可以自如地将这些 RDD 和来自其他 Spark SQL 数据源的 SchemaRDD 进行连接操作。在本节中,我们会讲解外部数据源以及这种使用 RDD 的方式。

9.3.1 Apache Hive

当从 Hive 中读取数据时,Spark SQL 支持任何 Hive 支持的存储格式(SerDe),包括文本文件、RCFiles、ORC、Parquet、Avro,以及 Protocol Buffer。

要把 Spark SQL 连接到已经部署好的 Hive 上,你需要提供一份 Hive 配置。你只需要把你 的 hive-site.xml 文件复制到 Spark 的 ./conf/ 目录下即可。如果你只是想探索一下 Spark SQL 而没有配置 hive-site.xml 文件,那么 Spark SQL 则会使用本地的 Hive 元数据仓,并且同样 可以轻松地将数据读取到 Hive 表中进行查询。

例 9-15 至例 9-17 展示了如何查询一张 Hive 表。Hive 示例表有两列,分别是 key(一个整 型值)和 value(一个字符串)。我们会在本章稍后介绍如何创建这样的表。

例 9-15:使用 Python 从 Hive 读取
from pyspark.sql import HiveContext
     hiveCtx = HiveContext(sc)
     rows = hiveCtx.sql("SELECT key, value FROM mytable")
     keys = rows.map(lambda row: row[0])
例 9-16:使用 Scala 从 Hive 读取
import org.apache.spark.sql.hive.HiveContext
     val hiveCtx = new HiveContext(sc)
     val rows = hiveCtx.sql("SELECT key, value FROM mytable")
     val keys = rows.map(row => row.getInt(0))
例 9-17:使用 Java 从 Hive 读取
     import org.apache.spark.sql.hive.HiveContext;
     import org.apache.spark.sql.Row;
     import org.apache.spark.sql.SchemaRDD;
 HiveContext hiveCtx = new HiveContext(sc);
     SchemaRDD rows = hiveCtx.sql("SELECT key, value FROM mytable");
     JavaRDD keys = rdd.toJavaRDD().map(new Function() {
       public Integer call(Row row) { return row.getInt(0); }
     });

9.3.2 Parquet

Parquet(http://parquet.apache.org/)是一种流行的列式存储格式,可以高效地存储具有嵌套 字段的记录。Parquet 格式经常在 Hadoop 生态圈中被使用,它也支持 Spark SQL 的全部数 据类型。Spark SQL 提供了直接读取和存储 Parquet 格式文件的方法。

首先,你可以通过 HiveContext.parquetFile 或者 SQLContext.parquetFile 来读取数据,如 例 9-18 所示。

例 9-18:Python 中的 Parquet 数据读取
# 从一个有name和favouriteAnimal字段的Parquet文件中读取数据 rows = hiveCtx.parquetFile(parquetFile)
names = rows.map(lambda row: row.name)
print "Everyone"
     print names.collect()
你也可以把 Parquet 文件注册为 Spark SQL 的临时表,并在这张表上运行查询语句。在例 9-18 中我们读取了数据,接下来可以参照例 9-19 所示的对数据进行查询。
例 9-19:Python 中的 Parquet 数据查询
# 寻找熊猫爱好者
tbl = rows.registerTempTable("people")
pandaFriends = hiveCtx.sql("SELECT name FROM people WHERE favouriteAnimal = \"panda\"")
print "Panda friends"
print pandaFriends.map(lambda row: row.name).collect()
最后,你可以使用 saveAsParquetFile() 把 SchemaRDD 的内容以 Parquet 格式保存,如例 9-20 所示。
例 9-20:Python 中的 Parquet 文件保存 pandaFriends.saveAsParquetFile("hdfs://...")

9.3.3 JSON

如果你有一个 JSON 文件,其中的记录遵循同样的结构信息,那么 Spark SQL 就可以通过 扫描文件推测出结构信息,并且让你可以使用名字访问对应字段(如例 9-21 所示)。如果 你在一个包含大量 JSON 文件的目录中进行尝试,你就会发现 Spark SQL 的结构信息推断 可以让你非常高效地操作数据,而无需编写专门的代码来读取不同结构的文件。

要读取 JSON 数据,只要调用 hiveCtx 中的 jsonFile() 方法即可,如例 9-22 至例 9-24 所示。如果你想获得从数据中推断出来的结构信息,可以在生成的 SchemaRDD 上调用 printSchema 方法(见例 9-25)。

例 9-21:输入记录 {"name": "Holden"}
     {"name": "Sparky The Bear", "lovesPandas":true,"knows": {"friends":["holden"]}}
例 9-22:在 Python 中使用 Spark SQL 读取 JSON 数据 input = hiveCtx.jsonFile(inputFile)
例 9-23:在 Scala 中使用 Spark SQL 读取 JSON 数据 val input = hiveCtx.jsonFile(inputFile)
例 9-24:在 Java 中使用 Spark SQL 读取 JSON 数据 SchemaRDD input = hiveCtx.jsonFile(jsonFile);
例 9-25:printSchema() 输出的结构信息
     root
      |-- knows: struct (nullable = true)
      |    |-- friends: array (nullable = true)
      |    |    |-- element: string (containsNull = false)
      |-- lovesPandas: boolean (nullable = true)
      |-- name: string (nullable = true)
你可以在例 9-26 中看到以某些推文生成的结构信息。

例 9-26:推文的部分结构

root
      |-- contributorsIDs: array (nullable = true)
      |    |-- element: string (containsNull = false)
      |-- createdAt: string (nullable = true)
      |-- currentUserRetweetId: integer (nullable = true)
      |-- hashtagEntities: array (nullable = true)
      |    |-- element: struct (containsNull = false)
       | |
| |
|    |
|-- id: long (nullable = true)
|-- inReplyToScreenName: string (nullable = true)
|-- inReplyToStatusId: long (nullable = true)
|-- end: integer (nullable = true)
|-- start: integer (nullable = true)
|-- text: string (nullable = true)
|-- inReplyToUserId: long (nullable = true)
|-- isFavorited: boolean (nullable = true)
|-- isPossiblySensitive: boolean (nullable = true)
|-- isTruncated: boolean (nullable = true)
|-- mediaEntities: array (nullable = true)
|    |-- element: struct (containsNull = false)
|    |    |-- displayURL: string (nullable = true)
|    |    |-- end: integer (nullable = true)

看到这样的结构,我们会自然而然地想到如何访问嵌套字段和数组字段这个问题。如果你 使用 Python,或已经把数据注册为了一张 SQL 表,你可以通过 . 来访问各个嵌套层级的 嵌套元素(比如 toplevel.nextlevel)。而在 SQL 中可以通过用 [element] 指定下标来访 问数组中的元素,如例 9-27 所示。

例 9-27:用 SQL 查询嵌套数据以及数组元素
select hashtagEntities[0].text from tweets LIMIT 1;

9.3.4 基于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")
例 9-30:在 Java 中基于 JavaBean 创建 SchemaRDD
     class HappyPerson implements Serializable {
       private String name;
       private String favouriteBeverage;
       public HappyPerson() {}
       public HappyPerson(String n, String b) {
         name = n; favouriteBeverage = b;
       }
       public String getName() { return name; }
       public void setName(String n) { name = n; }
       public String getFavouriteBeverage() { return favouriteBeverage; }
       public void setFavouriteBeverage(String b) { favouriteBeverage = b; }
     };
     ...
     ArrayList peopleList = new ArrayList();
     peopleList.add(new HappyPerson("holden", "coffee"));
     JavaRDD happyPeopleRDD = sc.parallelize(peopleList);
     SchemaRDD happyPeopleSchemaRDD = hiveCtx.applySchema(happyPeopleRDD,
       HappyPerson.class);
     happyPeopleSchemaRDD.registerTempTable("happy_people");

9.4 JDBC/ODBC服务器

Spark SQL 也提供 JDBC 连接支持,这对于让商业智能(BI)工具连接到 Spark 集群上以 及在多用户间共享一个集群的场景都非常有用。JDBC 服务器作为一个独立的 Spark 驱动 器程序运行,可以在多用户之间共享。任意一个客户端都可以在内存中缓存数据表,对表 进行查询。集群的资源以及缓存数据都在所有用户之间共享。

Spark SQL 的 JDBC 服务器与 Hive 中的 HiveServer2 相一致。由于使用了 Thrift 通信协议,它也 被称为“Thrift server”。注意,JDBC 服务器支持需要 Spark 在打开 Hive 支持的选项下编译。

服务器可以通过 Spark 目录中的 sbin/start-thriftserver.sh 启动(见例 9-31)。这个 脚本接受的参数选项大多与 spark-submit 相同(见 7.3 节)。默认情况下,服务器会在 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 选项。

例 9-31:启动 JDBC 服务器

./sbin/start-thriftserver.sh --master sparkMaster

Spark 也自带了 Beeline 客户端程序,我们可以使用它连接 JDBC 服务器,如例 9-32 和图 9-3 所示。这个简易的 SQL shell 可以让我们在服务器上运行命令。

例 9-32:使用 Beeline 连接 JDBC 服务器

 holden@hmbp2:~/repos/spark$ ./bin/beeline -u jdbc:hive2://localhost:10000
     Spark assembly has been built with Hive, including Datanucleus jars on classpath
     scan complete in 1ms
     Connecting to jdbc:hive2://localhost:10000
     Connected to: Spark SQL (version 1.2.0-SNAPSHOT)
     Driver: spark-assembly (version 1.2.0-SNAPSHOT)
     Transaction isolation: TRANSACTION_REPEATABLE_READ
     Beeline version 1.2.0-SNAPSHOT by Apache Hive
     0: jdbc:hive2://localhost:10000> show tables;
     +---------+
     | result  |
     +---------+
     | pokes   |
     +---------+
     1 row selected (1.182 seconds)
     0: jdbc:hive2://localhost:10000>

9.4.1 使用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 对于在多用户间共享的缓存数据表上进行快速的数据探索是非常有用的。

9.4.2 长生命周期的表与查询

使用 Spark SQL 的 JDBC 服务器的优点之一就是我们可以在多个不同程序之间共享缓存下 来的数据表。JDBC Thrift 服务器是一个单驱动器程序,这就使得共享成为了可能。如前一 节中所述,你只需要注册该数据表并对其运行 CACHE 命令,就可以利用缓存了。

除了 JDBC 服务器,Spark SQL 也支持一个可以作为单独的进程使用的简易 shell,可以通过 ./bin/spark-sql 启动。这个 shell 会连接到你设置在 conf/hive- site.xml 中的 Hive 的元数据仓。如果不存在这样的元数据仓,Spark SQL 也 会在本地新建一个。这个脚本主要对于本地开发比较有用。在共享的集群 上,你应该使用 JDBC 服务器,让各用户通过 beeline 进行连接。

9.5 用户自定义函数

用户自定义函数,也叫 UDF,可以让我们使用 Python/Java/Scala 注册自定义函数,并在 SQL 中调用。这种方法很常用,通常用来给机构内的 SQL 用户们提供高级功能支持,这样这些 用户就可以直接调用注册的函数而无需自己去通过编程来实现了。在 Spark SQL 中,编写 UDF 尤为简单。Spark SQL 不仅有自己的 UDF 接口,也支持已有的 Apache Hive UDF。

9.5.1 Spark SQL UDF

我们可以使用 Spark 支持的编程语言编写好函数,然后通过 Spark SQL 内建的方法传递进 来,非常便捷地注册我们自己的 UDF。在 Scala 和 Python 中,可以利用语言原生的函数和 lambda 语法的支持,而在 Java 中,则需要扩展对应的 UDF 类。

在例 9-36 和例 9-37 中,我们可以看到一个用来计算字符串长度的非常简易的 UDF,可以 用它来计算推文的长度。

例 9-36:Python 版本耳朵字符串长度 UDF
# 写一个求字符串长度的UDF
hiveCtx.registerFunction("strLenPython", lambda x: len(x), IntegerType()) lengthSchemaRDD = hiveCtx.sql("SELECT strLenPython('text') FROM tweets LIMIT 10")
例 9-37:Scala 版本的字符串长度 UDF registerFunction("strLenScala", (_: String).length)
     val tweetLength = hiveCtx.sql("SELECT strLenScala('tweet') FROM tweets LIMIT 10")
在 Java 中定义 UDF 需要一些额外的 import 声明。和在定义 RDD 函数时一样,根据我们 要实现的 UDF 的参数个数,需要扩展特定的类,如例 9-38 和例 9-39 所示。
例 9-38:Java UDF import 声明
// 导入UDF函数类以及数据类型
// 注意: 这些import路径可能会在将来的发行版中改变 import org.apache.spark.sql.api.java.UDF1;
import org.apache.spark.sql.types.DataTypes;
例 9-39:Java 版本的字符串长度 UDF
     hiveCtx.udf().register("stringLengthJava", new UDF1() {
         @Override
           public Integer call(String str) throws Exception {
           return str.length();
         }
       }, DataTypes.IntegerType);
     SchemaRDD tweetLength = hiveCtx.sql(
       "SELECT stringLengthJava('text') FROM tweets LIMIT 10");
     List lengths = tweetLength.collect();
     for (Row row : result) {
       System.out.println(row.get(0));
     }

9.5.2 Hive UDF

Spark SQL 也支持已有的 Hive UDF。标准的 Hive UDF 已经自动包含在了 Spark SQL 中。如 果需要支持自定义的 Hive UDF,我们要确保该 UDF 所在的 JAR 包已经包含在了应用中。需 要注意的是,如果使用的是 JDBC 服务器,也可以使用 --jars 命令行标记来添加 JAR.

9.6 Spark SQL性能

Spark SQL 提供的高级查询语言及附加的类型信息可以使 Spark SQL 数据查询更加高效。Spark SQL 不仅是给熟悉 SQL 的用户使用的。Spark SQL 使有条件的聚合操作变得非常容 易,比如对多个列进行求值(如例 9-40 所示)。利用 Spark SQL 则不再需要像之前讨论的那样创建一些特殊的对象来进行这种操作。

例 9-40:Spark SQL 多列求和
SELECT SUM(user.favouritesCount), SUM(retweetCount), user.id FROM tweets
       GROUP BY user.id

Spark SQL 可以利用其对类型的了解来高效地表示数据。当缓存数据时,Spark SQL 使用 内存式的列式存储。这不仅仅节约了缓存的空间,而且尽可能地减少了后续查询中针对某 几个字段查询时的数据读取。

谓词下推可以让 Spark SQL 将查询中的一些部分工作“下移”到查询引擎上。如果我们 只需在 Spark 中读取某些特定的记录,标准的方法是读入整个数据集,然后在上面执行筛 选条件。然而,在 Spark SQL 中,如果底层的数据存储支持只读取键值在一个范围内的记 录,或是其他某些限制条件,Spark SQL 就可以把查询语句中的筛选限制条件推到数据存 储层,从而大大减少需要读取的数据。

你可能感兴趣的:(Spark SQL)