Learning Spark笔记17-Spark SQL

第9章Spark SQL


Spark SQL提供3个主要的功能:
1.可以从结构化的数据源中加载数据(例如,JSON,Hive和Parquet)
2.可以使用SQL查询数据,无论是在Spark程序内还是使用诸如JDBC或ODBC这种连接器。
3.使用Spark程序的时候,Spark SQL提供了丰富的集成,在SQL和Python或Java或Scala代码,包括可以连接RDDs和SQL表,在SQL中暴露自定义函数。


为了实现这些功能,Spark SQL提供一个特别类型的RDD叫SchemaRDD。一个SchemaRDD是一个行对象的RDD,每个都是一个记录。一个SchemaRDD也知道其行的模式(即数据字段)。虽然SchemaRDDs看起来像是规则RDDs,在内部它们以更有效的方式存储数据,利用他们的schema。此外,它们提供RDD上不可用的新操作,例如运行SQL查询的能力。 SchemaRDD可以从外部数据源,查询结果或常规RDD创建。


在本章中,我们将首先展示如何在常规的Spark程序中使用SchemaRDD,加载和查询结构化的数据。我们会描述Spark SQL JDBC服务端,它允许您在共享服务器上运行Spark SQL,并将SQL shell或可视化工具(如Tableau)连接到它。最后,我们会讨论一些高级特性。Spark SQL是Spark较新的组件,它被包含在Spark1.3以后的版本中。



9.1连接到Spark SQL


Spark SQL可以不通过Apache Hive,Hadoop SQL引擎来建立。Spark SQL通过Hive的帮助可以访问Hive表,UDF(用户定义函数),SerDes(序列化和反序列化格式),和HiveQL。Hive库是不需要安装的,如果你下载的是二进制版本,就已经包含了Hive的支持。如果你是从源代码编译,你应该运行sbt/sbt -Phive。


如果您与Hive之间存在依赖冲突,您无法通过排除或着色来解决问题,则还可以在没有Hive的情况下构建并链接到Spark SQL。 在这种情况下,你链接到一个单独的Maven工件。


Example 9-1. Maven coordinates for Spark SQL with Hive support
groupId = org.apache.spark
artifactId = spark-hive_2.10
version = 1.2.0


当针对Spark SQL编程时,我们有两个入口点,取决于我们是否需要Hive支持。推荐的入口点是HiveContext提供访问HiveQL和Hive依赖的功能。更基本的SQLContext提供了不依赖于Hive的Spark SQL支持的一个子集。使用HiveContext不需要Hive的安装。


HiveQL是用Spark SQL的推荐查询语言。


最后,连接Spark SQL到已安装的Hive,你必须复制你的hive-site.xml文件到Spark的配置目录($SPARK_HOME/conf)。如果你没有安装Hive,Spark SQL也可以执行。


注意如果你没有安装Hive,Spark会创建自己的Hive metastore(metadata DB)在你程序的工作目录,叫做metastore_db。此外,如果你尝试使用HiveQL的CREATE TABLE语句(不是CREATE EXTERNAL TABLE),他们会被放置在默认文件系统的/user/hive/ware-house目录下(要么是你的本地文件系统,或HDFS如果你的hdfs-site.xml在你的classpath中)


9.2在应用程序中使用Spark SQL


更强大的方式是在Spark的应用程序中使用Spark SQL。这可以使我们更容易的加载数据,用SQL查询它,同时将它与Python,Java或Scala中的“常规”程序代码结合起来。


为了以这种方式使用Spark SQL,我们基于我们的SparkContext构建了一个HiveContext(或者希望精简版的SQLContext)。这个上下文为查询提供了额外的功能与Spark SQL数据进行交互。使用HiveContext,我们可以建立SchemaRDDs表达我们的结构化数据,使用SQL操作他们或者使用标准的RDD操作像map()。


9.2.1 安装Spark SQL


想要使用Spark SQL你需要在程序中添加一些import


Example 9-2. Scala SQL imports
// Import Spark SQL
import org.apache.spark.sql.hive.HiveContext
// Or if you can't have the hive dependencies
import org.apache.spark.sql.SQLContext


Example 9-3. Scala SQL implicits
// Create a Spark SQL HiveContext
val hiveCtx = ...
// Import the implicit conversions
import hiveCtx._


Example 9-4. Java SQL imports
// Import Spark SQL
import org.apache.spark.sql.hive.HiveContext;
// Or if you can't have the hive dependencies
import org.apache.spark.sql.SQLContext;
// Import the JavaSchemaRDD
import org.apache.spark.sql.SchemaRDD;
import org.apache.spark.sql.Row;


Example 9-5. Python SQL imports
# Import Spark SQL
from pyspark.sql import HiveContext, Row
# Or if you can't include the hive requirements
from pyspark.sql import SQLContext, Row


Example 9-6. Constructing a SQL context in Scala
val sc = new SparkContext(...)
val hiveCtx = new HiveContext(sc)


Example 9-7. Constructing a SQL context in Java
JavaSparkContext ctx = new JavaSparkContext(...);
SQLContext sqlCtx = new HiveContext(ctx);


Example 9-8. Constructing a SQL context in Python
hiveCtx = HiveContext(sc)


9.2.2 基础查询例子


对一个表进行查询,我们可以调用HiveContext或SQLContext的sql()方法。


Example 9-9. Loading and quering tweets in Scala
val input = hiveCtx.jsonFile(inputFile)
// Register the input schema RDD
input.registerTempTable("tweets")
// Select tweets based on the retweetCount
val topTweets = hiveCtx.sql("SELECT text, retweetCount FROM
 tweets ORDER BY retweetCount LIMIT 10")


Example 9-10. Loading and quering tweets in Java
SchemaRDD input = hiveCtx.jsonFile(inputFile);
// Register the input schema RDD
input.registerTempTable("tweets");
// Select tweets based on the retweetCount
SchemaRDD topTweets = hiveCtx.sql("SELECT text, retweetCount FROM
 tweets ORDER BY retweetCount LIMIT 10");


Example 9-11. Loading and quering tweets in Python
input = hiveCtx.jsonFile(inputFile)
# Register the input schema RDD
input.registerTempTable("tweets")
# Select tweets based on the retweetCount
topTweets = hiveCtx.sql("""SELECT text, retweetCount FROM
 tweets ORDER BY retweetCount LIMIT 10""")


 9.2.3 SchemaRDDs


加载数据和执行查询都返回SchemaRDD。SchemaRDDs与传统数据库中的表类似。在底层,一个SchemaRDD是由Row对象附带每列的schema的信息的RDD。行对象是基础类型的包装。


有一点需要注意的是,在下一个Spark的版本中,SchemaRDD可能会改名为DataFrame。


SchemaRDDs也是常规的RDD,所以你可以使用像map()和filter()这样的功能。然而,还有几个额外的功能。更重要的是,你可以注册任何SchemaRDD作为临时表,通过HiveContext.sql或SQLContext.sql。


9.2.3.1 使用Row对象


行对象表示SchemaRDD中的记录,并且只是固定长度的字段数组。在Scala/Java,Row对象有一些getter函数可以通过索引值获得每个字段的值。




Example 9-12. Accessing the text column (also first column) in the topTweets
SchemaRDD in Scala
val topTweetText = topTweets.map(row => row.getString(0))


Example 9-13. Accessing the text column (also first column) in the topTweets
SchemaRDD in Java
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。如果你不确定列的名字,我们可以打印schema。


Example 9-14. Accessing the text column in the topTweets SchemaRDD in Python
topTweetText = topTweets.map(lambda row: row.text)


9.2.4 缓存


缓存在Spark SQL中有些不同。因为我们知道每列的类型,Spark可以更有效的存储数据。为了确保我们使用高效的内存表示而不是完整的对象进行缓存,我们应该使用特殊的hiveCtx.cacheTable(“tableName”)方法。缓存表时Spark SQL以内存中列的格式表示数据。缓存表只有在我们的驱动程序运行时存在,如果程序退出,我们就需要重新缓存我们的数据。和RDD一样,当我们希望针对相同的数据运行多个任务或查询时,我们缓存表。


你也可以使用HiveQL/SQL语句缓存表。缓存或不缓存表只需要使用CACHE TABLE tableName或 UNCACHE TABLE tableName就可以了。


缓存SchemaRDDs会显示在Spark UI界面上。


9.3 加载保存数据


9.3.1 Apache Hive


当从Hive加载数据时,Spark SQL提供Hive的格式类型包括text文件,RCFiles,ORC,Parquet,Avro和Protocol Buffers。


连接Spark SQL到已存在的Hive安装,你需要提供Hive配置。复制你的hive-site.xml到Spark的./conf/文件夹下。如果您只是想探索一下,如果没有设置hive-site.xml,将使用本地Hive元存储,并且可以轻松地将数据加载到Hive表中以供稍后查询。


Example 9-15. Hive load in Python
from pyspark.sql import HiveContext
hiveCtx = HiveContext(sc)
rows = hiveCtx.sql("SELECT key, value FROM mytable")
keys = rows.map(lambda row: row[0])


Example 9-16. Hive load in Scala
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))


Example 9-17. Hive load in Java
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<Integer> keys = rdd.toJavaRDD().map(new Function<Row, Integer>() {
 public Integer call(Row row) { return row.getInt(0); }
});


9.3.2 Parquet


Parquet是比较流行的面向列的存储格式,可以嵌套字段。它经常被作为Hadoop的工具来使用,它提供了Spark SQL的数据类型。Spark SQL提供了从Parquet文件读取数据的方法。


首先,加载数据,你可以使用HiveContext.parquetFile或SQLContext.parquetFile


Example 9-18. Parquet load in Python
# Load some data in from a Parquet file with field's name and favouriteAnimal
rows = hiveCtx.parquetFile(parquetFile)
names = rows.map(lambda row: row.name)
print "Everyone"
print names.collect()


你也可以注册一个Parquet文件作为Spark SQL的临时表对它进行查询。


Example 9-19. Parquet query in Python
# Find the panda lovers
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()


最后你可以保存SchemaRDD到Parquet


Example 9-20. Parquet file save in Python
pandaFriends.saveAsParquetFile("hdfs://...")


9.3.3 JSON


如果你有一个JSON文件中的记录符合schema,Spark SQL可以通过扫描文件来推断schema,并让您按名称访问字段。如果您曾经发现自己正在盯着一个巨大的JSON记录目录,那么Spark SQL的模式推断是一种非常有效的方式,可以在不编写任何特殊的加载代码的情况下开始处理数据。


Example 9-21. Input records
{"name": "Holden"}
{"name":"Sparky The Bear", "lovesPandas":true, "knows":{"friends": ["holden"]}}


Example 9-22. Loading JSON with Spark SQL in Python
input = hiveCtx.jsonFile(inputFile)


Example 9-23. Loading JSON with Spark SQL in Scala
val input = hiveCtx.jsonFile(inputFile)


Example 9-24. Loading JSON with Spark SQL in Java
SchemaRDD input = hiveCtx.jsonFile(jsonFile);


Example 9-25. Resulting schema from printSchema()
root
 |-- knows: struct (nullable = true)
 | |-- friends: array (nullable = true)
 | | |-- element: string (containsNull = false)
 |-- lovesPandas: boolean (nullable = true)
 |-- name: string (nullable = true)


嵌套的字段该如何访问,我们只需要使用.(例如toplevel.nextlevel)。


Example 9-27. SQL query nested and array elements
select hashtagEntities[0].text from tweets LIMIT 1;


9.3.4 从RDDs


除了加载数据,我们也可以从一个RDD创建一个SchemaRDD


Example 9-28. Creating a SchemaRDD using Row and named tuple in Python
happyPeopleRDD = sc.parallelize([Row(name="holden", favouriteBeverage="coffee")])
happyPeopleSchemaRDD = hiveCtx.inferSchema(happyPeopleRDD)
happyPeopleSchemaRDD.registerTempTable("happy_people")


Example 9-29. Creating a SchemaRDD from case class in Scala
case class HappyPerson(handle: String, favouriteBeverage: String)
...
// Create a person and turn it into a Schema RDD
val happyPeopleRDD = sc.parallelize(List(HappyPerson("holden", "coffee")))
// Note: there is an implicit conversion
// that is equivalent to sqlCtx.createSchemaRDD(happyPeopleRDD)
happyPeopleRDD.registerTempTable("happy_people")


Example 9-30. Creating a SchemaRDD from a JavaBean in Java
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<HappyPerson> peopleList = new ArrayList<HappyPerson>();
peopleList.add(new HappyPerson("holden", "coffee"));
JavaRDD<HappyPerson> happyPeopleRDD = sc.parallelize(peopleList);
SchemaRDD happyPeopleSchemaRDD = hiveCtx.applySchema(happyPeopleRDD,
 HappyPerson.class);
happyPeopleSchemaRDD.registerTempTable("happy_people");


9.3.5 JDBC/ODBC服务端


Spark SQL也提供JDBC连接,将商业智能(BI)工具连接到Spark群集,以及跨多个用户共享群集。JDBC服务端作为独立的Spark driver程序运行,可以对多个客户端共享。任何客户端都可以在内存中缓存表,查询他们等等。集群资源和缓存的数据可以在他们之间共享。


Example 9-31. Launching the JDBC server
./sbin/start-thriftserver.sh --master sparkMaster


Example 9-32. Connecting to the JDBC server with Beeline
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>


Spark SQL的ODBC工具是Simba。


9.3.6 使用Beeline


使用Beeline客户端,可以使用标准的HvieQL命令创建、列举、查询表。


Example 9-33. Load table
> 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;


 Example 9-34. Show tables
> SHOW TABLES;
mytable
Time taken: 0.052 seconds


Example 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


9.3.7 长寿表和查询


使用Spark SQL的JDBC服务端的一个好处是可以在多个程序之间缓存表。这是可能的,因为JDBC Thrift服务器是一个单一的驱动程序。要做到这些你只需要注册表,然后运行CACHE命令就行了。


9.4 用户自定义函数


9.4.1 Spark SQL UDFs


Spark SQL提供了一个内置的方法来通过传入一个函数来轻松地注册UDF你的编程语言。在Python和Java我们需要指定SchemaRDD的返回的类型。在Java中类型可以在org.apache.spark.sql.api.java.DataType找到,在Python中我们要倒入DataType。


Example 9-36. Python string length UDF
# Make a UDF to tell us how long some text is
hiveCtx.registerFunction("strLenPython", lambda x: len(x), IntegerType())
lengthSchemaRDD = hiveCtx.sql("SELECT strLenPython('text') FROM tweets LIMIT 10")


Example 9-37. Scala string length UDF
registerFunction("strLenScala", (_: String).length)
val tweetLength = hiveCtx.sql("SELECT strLenScala('tweet') FROM tweets LIMIT 10")


Example 9-38. Java UDF imports
// Import UDF function class and DataTypes
// Note: these import paths may change in a future release
import org.apache.spark.sql.api.java.UDF1;
import org.apache.spark.sql.types.DataTypes;


Example 9-39. Java string length UDF
hiveCtx.udf().register("stringLengthJava", new UDF1<String, Integer>() {
 @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<Row> lengths = tweetLength.collect();
for (Row row : result) {
 System.out.println(row.get(0));
}


9.4.2 Hive UDFs


Spark SQL也可以使用已存在的Hive UDFs。标准的Hive UDFs已经自动包含了。如果你有自定义的UDF,重要的是确保UDF的jar包含在你的应用中。如果我们运行JDBC服务端,注意我们需要使用--jars命令标识。使用Hive UDF需要我们使用HiveContext代替规则的SQLContext。确定Hive UDF可用,可以使用hiveCtx.sql("CREATE TEMPORARY FUNCTION name AS class.function")


9.5 Spark SQL性能


Example 9-40. Spark SQL multiple sums
SELECT SUM(user.favouritesCount), SUM(retweetCount), user.id FROM tweets GROUP BY user.id


如果我们只想读取Spark中的某些记录,处理这个问题的标准方法是读入整个数据集,然后在其上执行一个过滤器。但是,在Spark SQL中,如果底层数据存储支持只检索关键字范围的子集或其他限制,则Spark SQL能够将查询中的限制向下推送到数据存储,从而导致读取的数据可能少得多。


9.5.1 性能调整选项


Example 9-41. Beeline command for enabling codegen
beeline> set spark.sql.codegen=true;
SET spark.sql.codegen=true
spark.sql.codegen=true
Time taken: 1.196 seconds


Example 9-42. Scala code for enabling codegen
conf.set("spark.sql.codegen", "true")


有几个选项值得关注。


spark.sql.codegen会使得Spark SQL在运行每个查询之前将其编译为java字节码。Codegen可以使长查询或经常反复的查询变得更快。然而,像是一个非常短(1-2秒)的查询,它可能会增加开销,因为它必须为每个查询运行一个编译器。Codegen也是实验性的,但我们建议尝试用于任何有大量查询的工作负载,或者反复重复相同的查询。


spark.sql.inMemoryColumnarStorage.batchSize当缓存SchemaRDDs,Spark SQL将RDD中的记录按此选项给出的大小批量分组在一起,每一批进行压缩。非常小批的数据进行低压缩,但是数据量太大就会有问题。如果表中的行非常大(例如,包含几百个字段或字符串的内容很大像是web页面),你就需要降低每一批的大小来防止内存溢出错误。如果没有设置该选项,默认的每一批的大小就可以了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

艺菲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值