背景

Spark SQL是Spark的一个模块,用于结构化数据的处理。

++++++++++++++  +++++++++++++++++++++
| SQL | | Dataset API |
++++++++++++++ +++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++
| Spark SQL |
+++++++++++++++++++++++++++++++++++++

使用Spark SQL的方式有2种,可以通过SQL或者Dataset API,这两种使用方式在本文都会涉及。

其中,通过SQL接口使用的方法具体又可分为3种:

  • 在程序中执行
  • 使用命令行
  • Jdbc/ODBC

这里只会介绍第一种方式。

Spark关于分布式数据集的抽象原本是RDD,Dataset是其升级版本。DataFrame是特殊的Dataset,它限定元素是按照命名的列来组织的,从这一点看相当于关系型数据库中的表。DataFrame等价于Dataset[Row],而且DataFrame是本文内容的核心。

DataFrame支持丰富的数据源:

+++++++++++++++++++
| 结构数据文件 |
| |
| +++++++++++++ | ++++++++++++++++
| | parquet | | | Hive table |
| +++++++++++++ | ++++++++++++++++
| |
| +++++++++++++ | ++++++++++++++++
| | csv | | | 关系数据库 |
| +++++++++++++ | ++++++++++++++++
| |
| +++++++++++++ | ++++++++++++++++
| | json | | | RDD |
| +++++++++++++ | ++++++++++++++++
| |
+++++++++++++++++++

这里的每一种数据源我们都会进行介绍。

本文主要介绍DataFrame和各数据源的IO操作,后面再写一篇文章介绍基于DataFrame的使用操作。即:本文关注如何得到一个DataFrame,如何将一个DataFrame进行持久化;后面要写的文章则关注如何使用DataFrame。

相关的开源项目demo-spark在github上。

数据源

这个图概述了本文介绍的主要内容,它也可以作为后续的备忘和参考。

这个图中包含两种箭头,宽箭头表示数据的流向,细箭头表示提供构造实例的方法。

比如DataFrame - DataFrameWriter - 存储的粗箭头,表示数据从内存通过DataFrameWriter流向存储;SparkSession - DataFrameReader的细箭头,表示可以从SparkSession对象创建DataFrameReader对象。

SparkSession

使用Spark SQL必须先构造SparkSession实例,时候之后需要调用其stop方法释放资源。模板如下:

val spark = org.apache.spark.sql.SparkSession
.builder()
.appName("Spark SQL basic demo")
.master("local")
.getOrCreate() // work with spark spark.stop()

下文中出现的所有的spark,如无特殊说明,都是指按照上述代码创建的SparkSession对象。

parquet

parquet文件是怎么样的?

PAR1 "&,   @   Alyssa   Ben ,
0 red 88,
@ \Hexample.avro.User % name% %favorite_color% 5 favorite_numbers %array <&% nameDH& &P5 favorite_color<@&P &?% (favorite_numbersarray
ZZ&? ? avro.schema?{"type":"record","name":"User","namespace":"example.avro","fields":[{"name":"name","type":"string"},{"name":"favorite_color","type":["string","null"]},{"name":"favorite_numbers","type":{"type":"array","items":"int"}}]} parquet-mr version 1.4.3 ? PAR1

它不是一个单纯的文本文件,包含了一些无法渲染的特殊字符。

parquet是默认的格式。从一个parquet文件读取数据的代码如下:

val usersDF = spark.read.load("src/main/resources/users.parquet")

spark.read返回DataFrameReader对象,其load方法加载文件中的数据并返回DataFrame对象。这个可以参照上文的闭环图理解。

我们可以调用Dataset#show()方法查看其内容:

usersDF.show()

输出结果:

+------+--------------+----------------+
| name|favorite_color|favorite_numbers|
+------+--------------+----------------+
|Alyssa| null| [3, 9, 15, 20]|
| Ben| red| []|
+------+--------------+----------------+

将一个DataFrame写到parquet文件的代码如下:

usersDF.write.save("output/parquet/")

DataFrame#write()方法返回DataFrameWriter对象实例,save方法将数据持久化为parquet格式的文件。save的参数是一个目录,而且要求最底层的目录是不存在的,下文类同。

另外一种写的方式是:

peopleDF.write.parquet("output/parquet/")

这两种方式的本质相同。

csv

csv是什么样的?

csv又称为逗号分隔符,即:使用逗号分隔一条数据中各字段的值。csv文件可以被excel解析,但是其本质只是一个文本文件。比如下面是一份csv文件的内容:

age,name
,Michael
30,Andy
19,Justin

第一行是表头,但是它和下面的数据并没有什么区别。所以在读取的时候,必须告诉读入器这个文件是有表头的,它(第一行)才会被解析成表头,否则就会被当成数据。

比如,解析表头的读:

spark.read.option("header", true).format("csv").load("output/csv/").show()

其中的option("header", true)就是告诉读入器这个文件是有表头的。

输出为:

+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+

不解析表头的读:

spark.read.format("csv").load("output/csv/").show()

输出为:

+----+-------+
| _c0| _c1|
+----+-------+
| age| name|
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+

spark自动构建了两个字段:_c0_c1,而把agename当成了一行数据。

另外一种简化的读法:

spark.read.option("header", true).csv("output/csv/")

其原理和上文中介绍的其他格式的文件相同。

将DataFrame写入到csv文件时也需要注意表头,将表头也写入文件的方式:

peopleDF.write.option("header", true).format("csv").save("output/csv/")

不写表头,只写数据的方式:

peopleDF.write.format("csv").save("output/csv/")

另外一种简化的写法是:

peopleDF.write.csv("output/csv/")

json

json文件是怎么样的?

上文中说过,DataFrame相当于关系数据库中的表,那么每一条数据相当于一行记录。关系数据库表又可以相当于一个类,每一行数据相当于具体的对象,所以DataFrame的每一条数据相当于一个对象。

DataFrame对要读取的json有特殊的要求:即每一条数据作为一行,整体不能包装成数组。比如:

{"name":"Michael"}
{"name":"Andy", "age":30}
{"name":"Justin", "age":19}

而一个标准的json应该是下面这样:

[{
"name": "Michael"
}, {
"name": "Andy",
"age": 30
}, {
"name": "Justin",
"age": 19
}
]

使用下面的方式读取json文件内容:

val peopleDF = spark.read.format("json").load(path)

这种读取的方式和上文parquet的读取方式一致,最终都是调用load方法。只是多了一段format("json"),这是因为parquet是默认的格式,而json不是,所以必须明确声明。

还有一种简化的方式,其本质还是上述的代码:

val peopleDF = spark.read.json(path)

将一个DataFrame写到json文件的方式:

peopleDF.write.format("json").save("output/json/")

同样的道理,和保存为parquet格式文件相比,这里多了一段format("json")代码。

另外一种简略的写法:

peopleDF.write.json("output/json/")

两者的本质是相同的。

jdbc

spark可以直接通过jdbc读取关系型数据库中指定的表。有两种读取的方式,一种是将所有的参数都作为option一条条设置:

val url = "jdbc:mysql://localhost:3306/vulcanus_ljl?autoReconnect=true&createDatabaseIfNotExist=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true"

val jdbcDF = spark.read
.format("jdbc")
.option("url", url)
.option("dbtable", "vulcanus_ljl.data_dict")
.option("user", "vulcanus_ljl")
.option("password", "mypassword")
.load()

另一种是预先将参数封装到Properties对象里:

val url = "jdbc:mysql://localhost:3306/vulcanus_ljl?autoReconnect=true&createDatabaseIfNotExist=true&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true"

val connectionProperties = new Properties()
connectionProperties.put("user", "vulcanus_ljl")
connectionProperties.put("password", "mypassword") val jdbcDF2 = spark.read
.jdbc(url, "vulcanus_ljl.data_dict", connectionProperties)

spark还可以通过jdbc将DataFrame写入到一张新表(表必须不存在),写入的方式同样分为两种:

jdbcDF.write
.format("jdbc")
.option("url", url)
.option("dbtable", "vulcanus_ljl.data_dict_temp1")
.option("user", "vulcanus_ljl")
.option("password", "mypassword")
.option("createTableColumnTypes", "dict_name varchar(60), dict_type varchar(60)") // 没有指定的字段使用默认的类型
.save()

jdbcDF2.write
.jdbc(url, "vulcanus_ljl.data_dict_temp2", connectionProperties)

其中,urlconnectionProperties的内容同上文读取时的设置。

写入时可以通过createTableColumnTypes设置指定多个字段的类型,其他没有指定的字段会使用默认的类型。

table

准备table

Spark SQL不需要依赖于一个已经存在的Hive,可以通过下面的代码生成本地的仓库:

import spark.sql

sql("CREATE TABLE IF NOT EXISTS src (key INT, value STRING)")
sql("LOAD DATA LOCAL INPATH 'src/main/resources/kv1.txt' INTO TABLE src")

CREATE TABLE...用来创建表,LOAD DATA用来将数据加载到表中。kv1.txt的文件内容如下:

238val_238
86val_86
311val_311
27val_27
165val_165
409val_409
255val_255
278val_278
98val_98
484val_484

读取

使用下面的代码读取指定表,并打印前5条数据:

spark.read.table("src").show(5)

输出:

+---+-------+
|key| value|
+---+-------+
|238|val_238|
| 86| val_86|
|311|val_311|
| 27| val_27|
|165|val_165|
+---+-------+

写入

使用下面的代码,将DataFrame的数据写入到一张新表:

tableDF.write.saveAsTable("src_bak")

如果要写入一张已经存在的表,需要按照下面的方式:

tableDF.write.mode(SaveMode.Append).saveAsTable("src_bak")

连接一个已存在的Hive

hive-site.xml放到项目的src/main/resources目录下,spark会自动识别该配置文件,之后所有针对Hive table的读写都是根据配置作用于一个已存在的Hive的。

text

text文件是不包含格式信息的,将text读取为DataFrame需要额外补充格式信息,具体又细分为两种情况:一种是格式是提前约定好的,另一种是在运行时才能确定格式。

下面针对这两种不同的情况分别介绍如何读写text的文件,text文件的内容如下:

Michael, 29
Andy, 30
Justin, 19

格式提前确定

读入text文件:

case class Person(name: String, age: Long)

private def runInferSchemaExample(spark: SparkSession): Unit = {
// $example on:schema_inferring$
// For implicit conversions from RDDs to DataFrames
import spark.implicits._ // Create an RDD of Person objects from a text file, convert it to a Dataframe
val peopleDF = spark.read.textFile("src/main/resources/people.txt")
.map(_.split(","))
.map(attributes => Person(attributes(0), attributes(1).trim.toInt))
.toDF()
peopleDF.show()
}

case class Person就是提前约定的text文件的格式,spark.read.textFile返回的是Dataset[String]类型,text的每一行作为一条数据。

import spark.implicits._是必要的,否则会报异常(我还没有对这块进行研究,无法给出详细的解释)。

写DataFrame到text文件,必须先把DataFrame转换成只有一列的数据集。比如对于上面的peopleDF,它的元素类型是Person,含有name和age两列,直接写就会抛出下面的异常:

Exception in thread "main" org.apache.spark.sql.AnalysisException: path file:/E:/projects/shouzheng/demo-spark/output/text already exists.;

写的方式如下:

peopleDF.map(person => person.getAs[String]("name") + "," + person.getAs[String]("age")).write.text("output/text")

格式在运行时确定

格式在运行时确定,是说我们不是在编码阶段预知数据的格式,所以无法预先定义好对应的case class。可能是因为我们需要解析很多的数据格式,每一种格式都定义case class不合适;可能是因为我们需要支持格式的动态扩展,能支持新的格式;可能是因为我们要处理的格式不稳定,可能发生变化...不管什么原因,其结果一致:我们只能通过更加动态的方式来解析数据的格式。

在这种情况下,我们依然需要获取数据的格式。初步获取的结果可能是常见的形式,比如字符串,然后解析并构造特定的类型StructType来表示数据的格式。

然后我们读取text文件,将内容转换为RDD[Row]类型,其中每一个元素的属性和StructType类型中声明的field是一一对应的。

准备好了代表schema的StructType和代表数据的RDD[Row],我们就可以创建DataFrame对象了:

    import spark.implicits._
val peopleRDD = spark.sparkContext.textFile("src/main/resources/people.txt") // The schema is encoded in a string
val schemaString = "name age" // Generate the schema based on the string of schema
val fields = schemaString.split(" ")
.map(fieldName => StructField(fieldName, StringType, nullable = true))
val schema = StructType(fields) // Convert records of the RDD (people) to Rows
val rowRDD = peopleRDD
.map(_.split(","))
.map(attributes => Row(attributes(0), attributes(1).trim)) // Apply the schema to the RDD
val peopleDF = spark.createDataFrame(rowRDD, schema)

写的方式同上,不再赘述。

总结

  1. 最核心的思想都在上面的那张闭环图上。
  2. 大部分数据源都有两种读写的方式:一种是指定format,一种是直接以格式名作为方法名。

Spark SQL数据源的更多相关文章

  1. 4. Spark SQL数据源

    4.1 通用加载/保存方法 4.1.1手动指定选项 Spark SQL的DataFrame接口支持多种数据源的操作.一个DataFrame可以进行RDDs方式的操作,也可以被注册为临时表.把DataF ...

  2. spark sql数据源--hive

    使用的是idea编辑器 spark sql从hive中读取数据的步骤:1.引入hive的jar包 2.将hive-site.xml放到resource下 3.spark sql声明对hive的支持 案 ...

  3. 大数据技术之_19_Spark学习_03_Spark SQL 应用解析 + Spark SQL 概述、解析 、数据源、实战 + 执行 Spark SQL 查询 + JDBC/ODBC 服务器

    第1章 Spark SQL 概述1.1 什么是 Spark SQL1.2 RDD vs DataFrames vs DataSet1.2.1 RDD1.2.2 DataFrame1.2.3 DataS ...

  4. DataFrame编程模型初谈与Spark SQL

    Spark SQL在Spark内核基础上提供了对结构化数据的处理,在Spark1.3版本中,Spark SQL不仅可以作为分布式的SQL查询引擎,还引入了新的DataFrame编程模型. 在Spark ...

  5. 【转载】Spark SQL之External DataSource外部数据源

    http://blog.csdn.net/oopsoom/article/details/42061077 一.Spark SQL External DataSource简介 随着Spark1.2的发 ...

  6. spark SQL学习(数据源之json)

    准备工作 数据文件students.json {"id":1, "name":"leo", "age":18} {&qu ...

  7. 第十一篇:Spark SQL 源码分析之 External DataSource外部数据源

    上周Spark1.2刚发布,周末在家没事,把这个特性给了解一下,顺便分析下源码,看一看这个特性是如何设计及实现的. /** Spark SQL源码分析系列文章*/ (Ps: External Data ...

  8. spark SQL学习(数据源之parquet)

    Parquet是面向分析型业务得列式存储格式 编程方式加载数据 代码示例 package wujiadong_sparkSQL import org.apache.spark.sql.SQLConte ...

  9. spark sql使用sequoiadb作为数据源

    目前没有实现,理一下思路,有3中途径: 1:spark core可以使用sequoiadb最为数据源,那么是否spark sql可以直接操作sequoiadb. 2: spark sql支持Hive, ...

随机推荐

  1. Mybatis第八篇【一级缓存、二级缓存、与ehcache整合】

    Mybatis缓存 缓存的意义 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题. myba ...

  2. 关于使用git和github的一点点感想

    第二篇博客 首先附上我的第一个java程序github地址: https://github.com/KingsC123456/FirstJavaHello 其次是关于我的github介绍,因为一直使用 ...

  3. Linux 文件查找

    在Linux系统的查找相关的命令: which 查看可执行文件的位置 whereis 查看文件的位置 locate 配合数据库查看文件位置 find 实际搜寻硬盘查询文件名称 whereis wher ...

  4. RG_4

    集训前半段马上就要结束了. 很多作业等待着我. 真希望作业君不喜欢我.

  5. java 基础语法 2

    一.语句

  6. spark与hive的集成

    一:介绍 1.在spark编译时支持hive 2.默认的db 当Spark在编译的时候给定了hive的支持参数,但是没有配置和hive的集成,此时默认使用hive自带的元数据管理:Derby数据库. ...

  7. HDFS概述(6)————用户手册

    目的 本文档是使用Hadoop分布式文件系统(HDFS)作为Hadoop集群或独立通用分布式文件系统的一部分的用户的起点.虽然HDFS旨在在许多环境中"正常工作",但HDFS的工作 ...

  8. submit text3的激活与使用

    SublimeText3常用快捷键和优秀插件 SublimeText是前端的一个神器,以其精简和可DIY而让广大fans疯狂.好吧不吹了直入正题 -_-!! 首先是安装,如果你有什么软件管家的话搜一下 ...

  9. POJ(1195)(单点修改,区间查询)(二维)

    题目大意 给定一个N*N的网格,刚开始每个网格的值都是0,接下来会对这些网格进行操作,有一下两种操作: 1."X Y A"对网格C[x][y]增加A 2."L B R T ...

  10. ServiceStack.Text / Newtonsoft.Json 两种json序列化性能比较

    JSON序列化现在应用非常多,尤其在前后端分离的情况下,平常大多数C#下都使用Newtonsoft.Json来操作,量少的情况下,还可以忽略,但量大的情况下就要考虑使用ServiceStack.Tex ...