简介
- Spark SQL可以从各种结构化数据源读取数据。
- 支持在Spark程序内使用SQl语句查询,也支持类似商业智能软件Tableau这样的外部工具通过标准数据库连接器(JDBC/ODBC)连接Spark SQL进行查询
- 在Spark程序内使用Spark SQL时,Spark SQL支持SQL与常规的Python/Java/Scala代码高度整合。
为了实现上述功能,Spark SQL提供了一种特殊的RDD,叫做SchemaRDD。在1.3版本以后SchemaRDD已经被DataFrame取代。
SchemaRDD是存放Row对象的RDD,每个Row对象代表一行记录,其还包含记录的结构信息。在SchemaRDD内部,SchemaRDD还支持RDD所没有的一些新操作,比如运行SQL查询。
连接Spark SQL
Apache Hive是Hadoop的SQL引擎,Spark编译时可以包含Hive支持,也可以不包含。包含Hive支持的Spark SQL可以支持Hive表访问、UDF(用户自定义函数)、SerDe(序列化格式和反序列化格式),以及Hive查询语言(HiveQL/HQL)。
当使用Spark SQL编程,根据是否支持Hive支持,有两个不同的入口,推荐使用HiveContext,它可以提供HiveQL以及其他依赖于Hive的功能的支持。更为基础的是SQLContext则支持Spark SQL功能的一个子集,子集去掉了需要依赖Hive的功能。使用HiveContext不需要事先部署好Hive推荐把HiveQL作为Spark SQL的查询语言。
若要把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,否则就是本地文件系统)。
基于Sparkcontext创建出一个HiveContext(如果使用去除Hive支持的Spark,则创建SQLContext),使用HiveContext可以创建出表示结构化数据的SchemaRDD,并且使用SQL或是类似map()的普通RDD操作来操作这些SchemaRDD。
使用Spark SQL
import SparkContext
import org.apache.spark.sql.hive.HiveContext
import org.apache.spark.sql.SQLContext
val sc = new SparkContext(...)
val hiveCtx = new HiveContext(sc)
基本查询
在Scala中读取并查询推文,根据retweetCount字段选出最热门的推文
val input = hiveCtx.jsonFile(inputFile)
input.registerTempTable("tweets")
val topTweets = hiveCtx.sql("SELECT text,retweetCount FROM tweets ORDER BY retweetCount LIMIT 10")
SchemaRDD
读取和查询都会返回ScheamRDD
。SchemaRDD仍然是RDD,我们可以对其应用已有的RDD转化操作,比如map()和filter(),我们也可以将SchemaRDD注册为临时表,这样就可以使用HiveContext.sql或SQLContext.sql对它进行查询。
使用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对应的值作为字符串返回
val topTweetText = topTweets.map(row => row.getString(0))
缓存:Spark SQL的缓存机制与Spark的稍有不同,由于我们知道每个列的类型关系,所以Spark可以高效的存储数据,为了确保使用更节约内存的表示方式进行缓存而不是储存整个对象,应当使用专门的hiveCtx.cacheTable("tableName")
方法。Spark SQL使用一种列式存储格式在内存中保存数据。这些缓存下来的表只会在驱动器程序的声明周期里保存在内存中,所以如果驱动器进程退出,就需要重新缓存数据。和缓存RDD时的时机一样,如果想在同样的数据上多次运行任务或查询,就应该把这些数据表缓存起来。
实际上RDD原有的cache()方法也会引发一次对cacheTable()方法的调用。
我们也可以使用HiveQL/SQL语句来缓存表。只需要运行CACHE TABLE tableName
或UNCACHE TABLE tableName
来缓存或删除已有的缓存。被缓存的SchemaRDD也会与RDd相似的方式在Spark的应用用户界面呈现。
读取和存储数据
从Hive读取数据支持任何Hive支持的存储格式(SerDe),包括文本文件、RCFiles、ORC、Parquet、Avro以及Protocol Buffer。
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))
Parquet
Parquet是一种流行的列式存储格式,可以高效的存储具有嵌套字段的记录。
Python中的Parquet数据读取
rows = hiveCtx.parquetFile(parquetFile)
names = rows.map(lambda row: row.name)
print(name.collect())
将Parquet注册为临时表运行查询,最后保存
tbl = rows.registerTampTable("people")
pandaFriends = hiveCtx.sql("SELECT name FROM people WHERE favouriteAnimal = \"panda\"")
print(pandaFriends.map(lambda row: row.name).collect())
pandaFriends.saveAsParquetFile("hdfs://...")
JSON
如果你有一个JSON文件,其中的记录遵循同样的结构信息,Spark SQL就可以通过扫描文件推测结构信息,并且可以通过使用名字访问对应字段。如果你在一个包含大量JSON文件的目录中进行尝试,你会发现Spark SQL的结构信息推断会让你非常高效地操作数据,而无需编写专门的代码读取不同结构的文件。
读取JSON数据,只要调用hiveCtx中的jsonFile()方法,如果获得从数据中推断出来的结构信息,可以在生成的SchemaRDD上调用printSchema方法。
{"name":"Holden"}
{"name":"Sparky The Bear","lovePandas":"true","know":{"friend":["holden"]}}
val input = hiveCtx.jsonFile(inputFile)
在SQL中可以通过用[element]指定下标来访问数组中的元素。
select hashtagEntities[0].text from tweets LIMIT 1;
基于RDD
基于RDD可以创建SchemaRDD,在Scala中,带有case class 的RDD可以隐式转换成Schema RDD
case class HappyPerson(handle: String, favouriteBeverage: String)
val happyPeopleRDD = sc.parallelize(List(HappyPerson("holden","coffee")))
//注意:此处发生了隐式转换
//该转换等价于sqlCtx.createSchemaRDD(happyPeopleRDD)
happyPeopleRDD.registerTempTable("happy_people")
JDBC/ODBC服务器
Spark SQL也提供JDBC连接支持,这样商业智能工具连接到Spark集群上以及在多用户间共享一个集群的场景非常有用。JDBC服务器作为一个独立的Spark驱动器程序运行,可以在多用户间共享。任何一个客户端都可以在内存中缓存数据表,对表进行查询。集群的资源以及缓存数据都在所有用户之间共享。
Spark的JDBC服务器与Hive中的HiveServer2相一致。由于使用了Thift通信协议,它也被称为"Thift server"。JDBC服务器支持需要Spark在打开Hive支持的选项下编译。
JDBC服务器通过Spark目录中的sbin/start-thiftserver.sh
启动。这个脚本接受的参数选项大多与spark-submit
相同。服务器会在localhost:10000
上监听,可以通过环境变量HIVE_SERVER2_THRIFT_PORT
和HIVE_SERVER2_BIND_PORT
修改这些配置,也可以通过Hive配置选项(hive.server2.thrift.port和hive.server2.thrift.bind.host)来修改,也可以通过命令行参数–hiveconf property=value 来设置Hive选项。
./sbin/start-thriftserver.sh --master sparkMaster
Spark也自带了Beeline客户端程序,我们可以使用它连接JDBC服务器,这里不详细叙述。Beeline shell对于多用户间共享的缓存数据表上进行快速的数据搜索是非常有用的。
使用Spark SQL的JDBC服务器的优点之一就是我们可以在多个不同程序之间共享缓存下来的数据表。
除了JBDC服务器,Spark SQL也支持一个可以作为单独的进程使用的简易shell,可以通过./bin/spark-sql
启动。这个shell会连接到你设置在conf/hive-site.xml
中的Hive的元数据仓,如果不存在,Spark SQL也会在本地新建一个。这个脚本主要对于本地开发比较有用,在共享的集群上,应该使用JDBC服务器,让各用户通过beeline连接。