SparkSQL
SparkSQL技术允许使用关系型查询语句SQL、HiveSQL或者在Spark上面执行的scala
这部分的核心是一种新型的RDD-SchemaRDD。SchemaRDD由行对象(Row Objects)以及描述了每一列的数据类型的一个架构组成(Schema)一个Schema类似于传统数据库中的一张表,可以从现有的RDD文件、文件片段、json数据、或者从一个HiveQL来源的单元数据中产生。
所有这些例子都包含在Spark distribution中,可以在Spark-shell中运行
SparkSQL是一个alpha级别的构成,而我们将会减少一些API方法,这在Spark将来的版本中可能会体现出来
快速开始
SparkSQL的入口是一个SQLContext类,或者它的一个子类。创建它你需要一个SparkContext
val sc:SparkContext //记得我们说过的shell自带的SparkContext吧
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
//createSchemaRDD用于隐式的将RDD转换成为SchemaRDD
import sqlContext.createSchemaRDD
除了最基本的SQLContext,你还可以创建一个HiveContext,这是SQLContext提供功能的一个超集。附加的功能包括使用更完整的HiveQL解析器写查询语句的能力,而且能使用HiveUDFS,能够从Hive中读取数据。使用HiveContext你不需要一个现成的Hive安装。和SQLContext处理所有数据源一样,HiveContext只不过是单独封装,在Spark中包含了所有的Hive依赖。如果你使用HiveContext不是问题那么你可以尝试使用Spark1.2以上版本。未来的版本将致力于将SQLContext的特性和HIveContext等同的规模上。
用于解析SQL查询,你可以通过spark.sql.dialect选择特定的形式。这个参数可以使用setConf方法在SQLContext中设置或者使用set命令的key=value形式设置。对于SQLContext,唯一的方式是“sql”。SparkSQL对SQL使用了简单的形式对SQL进行解析,而在HiveContext,默认为“hivesql”,虽然“sql”也可以,但是由于HiveQL解析更完整,所以建议在大多数情况下使用。
数据源
SparkSQL通过SchemaRDD接口支持多种数据源。一个SchemaRDD可以像普通RDD一样进行工作,也可以注册为一张临时表。注册一个SchemaRDD临时表允许你运行SQL来查询数据。本节来描述数据加载到SchemaRDD中的各种方法
RDDS
SparkSQL使用两种不同的方法将现有的RDD转换成SchemaRDD。第一种方法使用反射来推断RDD所包含对象的具体结构类型。基于反射的方法会导致更加简洁的代码,效果很好,尤其是当你已经知道Schema模式在Spark中的应用之后。
创建SchamaRDD的第二种方法是通过一种编程接口,允许你构建一个Schema,然后将它应用到一个现有的RDD中。虽然这种方法是更加详细的,它允许你构建一个SchemaRDD但是它的列和类型,直到运行时才会知道
使用反射机制转换
SparkSQL的scala接口支持将RDD中的实例自动转换到SchemaRDD。这种情况下,类型定义表的架构。类的名字反射作为列的名字使用。类也可以嵌套包含复杂类型,例如Sequences或者Arrays。这些可以隐式转换成为SchemaRDD然后被注册为一张表。表可以用于随后的SQL语句使用
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
//隐式转换一个RDD到SchmaRDD
import sqlContext.createSchemaRDD
//定义类,注意:scala2.10例子中只支持22个领域,要解决这个限制
//你可以使用自定义类,实现接口
case class Person(name:String,age:Int)
//创建一个关于Person的RDD并且注册成为table的形式
val people =sc.textFile("examples/src/main/resources/people.txt").map(_split(",")).map(p=>Persion(p(0),p(1).trim.toInt))
people.registerTempTable("people")
//SQL语句可以在SQLContext中的sql方法执行
val teenagers = sqlContext.sql("SELECT name FROM people WHERE age >= 13 ANDage <=19")
//SQL查询结果的SchemaRDD支持所有正常的RDD操作,行列中的结果可以被顺序访问
teenagers.map(t=> "Name:"+t(0)).collect().foreach(println)
以编程的方式指定
当类定义不能够被提前(例如,记录结构是一个字符串,编码或者文本数据集将被解析和映射为不同的用户)一个SchemaRDD可以以编程方式通过下面三个步骤创建
1、从原来的RDD创建行
2、创建一个structtype对步骤1中创建的结构进行匹配
3、通过SQLContext的applySchema方法应用这个结果
举例
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
//创建RDD
val people = sc.textFile("examples/src/main/resources/people.txt")
//schema用一种形式进行编写
val schemaString = "name age"
//引入 sparkSQL
import org.apache.spark.sql._
//基于map生成Schema形式的字符串模式
val schema = StructType(schemaString.split(" ").map(fieldName =>StructField(firstName,StringType,true)))
//将RDD加入行
val rowRDD = people.map(_.split(",")).map(p => Row(p(0),p(1).trim))
//将Schema应用到RDD
val peopleSchemaRDD = sqlContext.applySchema(rowRDD,schema)
//将SchemaRDD注册为table
peopleSchemaRDD.regiesterTempTable("people")
//通过SQLContext执行sql语句
val results = sqlContext.sql("SELECT name FROM people")
//结果可以被正常访问
results.map(t=> "Name:"+t(0)).collect().foreach(println)
Parquet文件
Parquet是一种列存储格式,由很多其他的数据处理系统提供支持SparkSQL提供了读写Parquet文件自动保存原始数据的支持
编程形式加载数据
使用从上面的例子中获得的数据
//SQLContext以前的例子,在这个例子中使用
//createSchemaRDD用于将RDD隐式转换成一个SchemaRDD
import sqlContext.createSchemaRDD
val people:RDD[Persion]=...//样例类对象,从上个例子引申过来
//从上面创建的文件中读取parquet文件,Parquet文件是以自述文件的形式保存的
//加载Parquet文件的结果也是一个SchemaRDD
val parquetFile = sqlContext.parquetFile("people.parquet")
//Parquet文件可以注册为表,然后使用SQL语句进行处理
parquetFile.regiesterTempTable("parquetFile")
valteenagers = sqlContext.sql("SELECT name FROM parquetFile WHERE age >=13 AND age <=19")
teenagers.map(t=> "Name:"+t(0)).collect().foreach(println)
配置
配置形式可以使对SQLContext的setConf方法或者在运行SQL时采用SET key=value方式使用
名称 | 默认值 | 描述 |
spark.sql.parquet.binaryAsString | false | 一些其他的Parquet-producting系统,特别是Impala和旧的SparkSQL版本,二进制和字符串的输出没有什么不同。这个标识告诉SparkSQL提供解释二进制数据文件和字符串文件的兼容性 |
spark.sql.parquet.cacheMetadata | false | 打开Parquet schema的元数据缓存,可以加快静态数据的查询 |
spark.sql.parquet.compression.codec | snappy | 压缩输出Parquet文件,可接受的值:uncompressed(未压缩),snappy,gzip,lzo |
JSON数据集
SparkSQL可以自动判断出一个JSON数据集并且把它加载成为一个SchemaRDD,这种转换可以通过使用一个SQLContext的两个方法进行
jsonFIle-文件从一个JSON文件每一行作为一个JSON对象进行加载
jsonRDD-从现有的RDD产生,RDD的每个元素包含一个JSON对象字符串
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
//一个json数据集,path指定了一个文本文件或者一个文本文件文件夹
val path = "examples/src/main/resources/people.json"
//从路径中的文件创建一个SchemaRDD
val people = sqlContext.jsonFile(path)
//推断模式可以使用printSchema()查看
people.printSchema()
//root
//|--age:integer(nullable = true)
//|--name:string(nullable = true)
//注册一个SchemaRDD 到table形式
people.registerTempTable("people")
//sqlContext的sql方法
val teenagers = sqlContext.sql("SELECT nam FROM people WHERE age >=13 ANDage <= 19")
//或者,一个SchemaRDD可以从json数据集创建。RDD[]每一个字符串作为一个json对象
val anotherPeopleRDD = sc.parallelize(
"""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}"""::Nil)
valanotherPeople = sqlContext.jsonRDD(anotherPeopleRDD)
Hive表
SparkSQL也支持从ApacheHive读写数据。然而,由于Hive有大量的依赖,这些不是Spark的默认组件,为了使用Hive必须首先运行"sbt/sbt -Phive assembly/assembly"(或者使用-Phive for maven)该命令生成一个新的封装,包括Hive,注意这个封装也是必须对所有节点存在都是存在的,因为他们要访问序列化还有反序列化库(SerDes)来存储访问Hive数据
Hive配置文件可以将你的hive-site.xml文件放在./conf中
使用Hive工作时,必须创建一个HiveContext,它继承自SQLContext,并增加了寻找和使用元数据信息还有HiveQL编写的数据查询的支持。无论有没有一个现有的Hive用户都可以创建HiveContext。如果没有配置hive-site.xml上下文会自动在当前目录创建metastore_db和warehouse
val sqlContext = new org.apache.spark.sql.hive.HiveContext(sc)
sqlContext.sql("CREATETABLE IF NOT EXISTS src (key INT,value STRING)")
sqlContext.sql("LOADDATA LOCAL INPATH 'examples/src/main/resources/kv1.txt'INTO TABLE src")
//查询表示SQL
sqlContext.sql("FROMsrc SELECT key,value").collect().foreach(println)
性能优化
对于某些工作负载可以通过在内存中缓存数据来提高性能,或者通过一些实验性的方案。
在内存中缓存数据
SparkSQL可以缓存表进行类似内存中的列存储格式,通过调用sqlContext.cacheTable("tableName")使用。然后SparkSQL扫描时候只需要对列进行自动调整压缩来减少内存使用和GC压力。你可以使用sqlContext.uncacheTable("tableName")去删除内存中的表
请注意,如果你调用schemaRDD.cache()而不是sqlContext.cacheTable(...)表将不会被使用列存储格式缓存在内存中。因此,sqlContext.cacheTable()强烈推荐这样使用
配置缓存到内存中,可以使用sqlContext的sqlConf方法或者使用SQL设置key=value方式设置。
属性 | 默认值 | 描述 |
spark.sql.inMemoryColumnarStorage.compressed | false | 设置为True时,SparkSQL将自动选择一个基于数据统计的列形式的压缩编码解码器 |
spark.sql.inMemoryColumnarStorage.batchSize | 1000 | 控制列缓存的批量大小,较大的值可以提高内存的压缩利用率,但是对于缓存面向对象数据来说是冒险的 |
其它配置选项
下列选项也可以被用来调整执行查询性能。一种可能的情况是在未来更多的优化是自动执行的
属性 | 默认值 | 描述 |
spark.sql.autoBroadcastJoinThreshold | 10000 | 配置信息将被广播加入到所有节点,worker node上面所执行字节的最大大小。通过将该值设置为-1时候广播可以被禁用。值得注意的是,目前统计数据仅支持Hive元数据创建表指令'ANALYZE TABLE <tableName> COMPUTE STATISTIS noscan'的运行 |
spark.sql.codegen | false | 设置为true时,代码将在一个特定的查询表达式值的运行时生成。对于某些查询的复杂表达式,这个选项可以导致显著的speed-ups然而,简单的查询,这样做可能导致查询缓慢。 |
spark.sql.shuffle.partitions | 200 | 配置join和聚合操作时候shuffle的分区数量 |
其它SQL接口
Spark运行SQL查询时候不需要编写任何代码接口
运行Thrift服务
ThriftJDBC服务实现了对于Hive0.12的HiveServer2。你可以测试带有Spark和Hive0.12的JDBC服务脚本
开启JDBC服务,运行以下脚本
./sbin/start-thriftserver.sh
这个脚本接受所有bin/spark-submit命令执行选项,再加上一个--hiveconf选项指定Hive的性质。你可以运行./sbin/start-thriftserver.sh--help来查看一个完整的选项列表。默认情况下,服务器监听localhost:10000你可以通过环境变量重写这个行为:
export HIVE_SERVER2_THRIFT_PORT=<listening-port>
export HIVE_SERVER2_THRIFT_BIND_HOST=<listening-host>
./sbin/start-thriftserver.sh\
--master<master-uri>
...
或者系统属性
./sbin/start-thriftserver2.sh\
--hiveconfhive.server2.thrift.port=<listening-port>
--hiveconfhive.server2.thrift.bind.host=<listening-host>
--master<master-uri>
...
现在你可以直接连接Thrift JDBC来测试
./bin/beeline
连接JDBCServer
beeline>! connect jdbc:hive2://localhost:10000
beeline会要求你输入用户名密码,在非安全模式,只要在安全模式输入用户名和密码。
配置文件hive-site.xml在 conf下面
你也可以直接使用来自hive的脚本。
使用SparkSQL CLI
SparkSQLCLI是一个方便的工具,可以在本地模式下使用Hive元数据服务并且从命令行输入查询。请注意,SparkSQLCLI不能使用ThriftJDBC服务。
启动SQLCLI,从以下目录运行
./bin/spark-sql
Hive参数是在conf目录下的hive-site.xml文件设置,你可以使用./bin/spark-sql --help查看所有可以使用的选项列表
与其它系统的兼容性
Shark的用户迁移指南
调度
为一个JDBC客户端会话建立一个公平的调度器,用户可以设置spark.sql.thriftserver.scheduler.pool变量
SET spark.sql.thriftserver.scheduler.pool=accounting;
reduce数量
Shark中,默认的Reduce是1由mapred.reduce.tasks控制。SparkSQL不使用这个属性但有另一个属性spark.sql.shuffle.partitions默认值是200,用户可以通过设置自定义属性
SET spark.sql.shuffle/partitions=10;
SELECT page,count(*) c FROM logs_last_month_cached GROUP BY page ORDER BY c DESC LIMIT 10;
你可以把这个属性在hive-site.xml中设置以覆盖默认值。
现在,这个mapred.reduce.tasks依然是公认的,并自动转化为spark.sql.shuffle.partitions
缓存
shark.cache这个属性不再存在,并与_cached名字的表一起不再被缓存。相反,我们提供缓存表CACHE TABLE和UNCACHE TABLE语句让用户控制表进行缓存
CACHE TABLE logs_last_month;
UNCACHE TABLE logs_last_month;
注意:CACHE TABLE是惰性的,类似于在RDD进行缓存,这个命令是作为一个标志tbl来确保分区缓存,当计算没有触及到的时候,不进行缓存,当使用到的时候,则进行缓存,你可以简单地进行表计数缓存之后立即执行:
CACHE TABLE logs_last_month;
SELECT COUNT(1) FROM logs_last_month;
一些缓存相关的特征还不支持以下几方面:
*用户定义的分区级缓存策略
*RDD重装
*在内存中缓存写策略
ApacheHive的兼容性
SparkSQL的设计是对Hive元数据仓库进行兼容,SerDes和UDFS。目前Spark是基于Hive0.12.0版本
现有的Hive部署
SparkSQL的JDBC服务设计初衷是“开箱”与现有Hive进行兼容。你不需要修改现有的Hive元数据或者更改你的现有表分区
对Hive的支持
Spark支持绝大多数的Hive功能,例如:
Hive查询语句,包括 select, group by, order by, cluster by, sort by
所有的Hive符号
关系符:=,⇔
,
==
,
<>
,
<
,
>
,
>=
,
<=
等等
(
⇔
?)
计算符:
+
,
-
,
*
,
/
,
%
等等
逻辑操作符:AND,&&,OR,||,等等
复合类型操作
数学函数:sign,ln,cos等等
字符函数:instr,length,printf等等
用户自定义函数(UDF)
用户自定义聚合函数(UDAF)
用户自定义序列化格式(SerDes)
Joinsjoin,{left|right|full}outer join,left semi join,cross join
Unions
子查询
select col from (select a+b as col from t1)t2
抽样(sampling)
解释(Explain)
分区表(partitions table)
所有的HiveRDD操作包括create table,create table as select,alter table
众多的Hive类型,包括tinyint,smallint,int,bigint,boolean,float,double,string,binary,timestamp,array<>,map<>,struct<>
不支持的Hive功能
下面是一个Spark不支持的Hive功能列表,绝大多数是很少使用的功能
主要的特性
Spark不支持插入形式的动态分区表
分桶表或者hash分区,Spark不支持分桶
深层的特性
使用不同输入格式的分区表,SparkSQL需要表分区需要有相同的输入格式
非等外连接,在一种罕见的情况下,使用外连接与非等值的连接条件(例如key<10)Spark将给出错误的结果为null的元组
union和日期类型
union join
一次查询多次插入(single query multi insert)
列统计信息的收集,SparkSQL不会背负所收集的保存在hive元数据的列统计信息
Hive 输入、输出格式化
对于cli的输出,结果显示回cli,SparkSQL只支持TextOutputFormat
Hadoop存档格式(archive)
Hive优化
少数的Hive优化是不包含在Spark中的,其中的一些(比如索引)是不太重要的Spark的计算模型,其他的比如槽位的释放等
块位图索引(Block level bitmap indexes)以及虚拟索引(virtualcolumns)用于建立索引
自动将map join,join一个大表与多个小表,Hive会自动将其转换为一个map join,我们将在下一个版本进行自动转换
自动确定连接和reducer、join、groupby数量目前在SparkSQL,你需要控制任务并行度,然后用shuffle调用“SET spark.sql.shuffle.partitions=[num_tasks];”
元数据查询,仅返回元数据结果,SparkSQL还推出任务计算结果
倾斜数据标志,SparkSQL不支持Hive的数据倾斜标志
合并多个小文件的查询结果,如果结果输出包含多个小文件,Hive可以合并小的文件到大文件,避免溢出HDFS元数据,SparkSQL不支持
关系查询语句
关系查询语句(Language-Integrated queries),目前只支持Scala
SparkSQL还支持特定于域的语言编写查询,再一次,使用上次的示例
val sqlContext = new org.apache.spark.sql.SQLContext(sc)
import sqlContext._
val people:RDD[Person]=...
//结果类似于“SELECT nameFROM people WHERE age >= 10 AND age <=19”
val teenagers = people.where('age >= 10).where('age <=19).select('name)
teenagears.map(t => "Name:" +t(0)).collect().foreach(println)
DSL使用Scala基础符号表中的列,这个标示符是带有标识的(')这些符号表达式将被隐式转换,通过SQL执行引擎进行解析。一个完整的功能支持列表可以参考scaladoc实现
Spark的数据类型参考
所有数据类型都在org.apache.spark.sql包中,你可以进行导入
import org.apache.spark.sql._
类型 | Scala类型 | API访问或创建 |
ByteType | Byte | ByteType |
ShortType | Short | ShortType |
IntegerType | Int | IntegerType |
LongType | Long | LongType |
FloatType | Float | FloatType |
DOubleType | Double | DoubleType |
DecimalType | Scala.math.sql.BigDecimal | DecimalType |
StringType | String | StringType |
BinaryType | Array[Byte] | BinaryType |
BooleanType | Boolean | BooleanType |
TimestampType | java.sql.Timestamp | TimeStampType |
ArrayType | scala.collection.Seq | ArrayType(elementType,[containsNull])注意:默认值containsNull为false |
MapType | scala.collection.Map | MapType(keyType,valueType,[calueContainsNull])注意:valueContainsNull默认值为NULL |
StructType | org.apache.spark.sql.Row | StructType(fields)注意:fields是一个序列的结构域,两个域不允许拥有相同名称 |
StructField | 在该字段的Scala值类型(例如,整型数据类型为一个integerType的StructField) | StructField(name,dataType,nullable) |