背景

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. SpringMVC的数据格式化-注解驱动的属性格式化

    一.什么是注解驱动的属性格式化? --在bean的属性中设置,SpringMVC处理 方法参数绑定数据.模型数据输出时自动通过注解应用格式化的功能. 二.注解类型 1.DateTimeFormat @ ...

  2. js 第一课

    什么是JavaScript JavaScript是一种脚本语言,运行在网页上.无需安装编译器.只要在网页浏览器上就能运行 一般JavaScript与HTML合作使用. 例如 <html> ...

  3. Linux 常用命令之二

    整理以前学习Linux的笔记. 查找目录.查看当前所在路径.新建文件.查看文件内容.修改文件内容.压缩文件操作.搜索命令.管道命令.查看进程.终止进程.查看端口. 7,命令find--查找目录 fin ...

  4. 移动端与PHP服务端接口通信流程设计(增强版)

    增强地方一: 再增加2张表,一个接口表,一个授权表,设计参考如下: 接口表 字段名 字段类型 注释 api_id int 接口ID api_name varchar(120) 接口名,以"/ ...

  5. Linux 内核模块程序结构

    1.内核加载函数 即我们常说的内核入口函数,当内核被加载的时候调用,在内核入口函数中多进行设备的注册和初始化,其中最常用的莫过于module_init().insmod xxx.ko的时候调用. 通常 ...

  6. ActiveMQ 入门helloworld

    1.下载安装ActiveMQ 官网下载地址:http://activemq.apache.org/download.html ActiveMQ 提供了Windows 和Linux.Unix 等几个版本 ...

  7. 代码与编程(java基础)

    代码与编程(面试与笔试java) 1.写一个Singleton出来 Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在. 一般Singleton模式通常有几种种 ...

  8. IDEA- idea代码调试debug

    IDEA有很多的快捷键,下面整理Debug的快捷键,方便自己使用!(阅读本篇可能花费您2分钟,需要多的实践练习) F9 resume programe 恢复程序 Alt+F10 show execut ...

  9. Dynamic Inversions II 逆序数的性质 树状数组求逆序数

    Dynamic Inversions II Time Limit: 6000/3000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Other ...

  10. handlebar JS模板使用笔记

    直接上代码: (定义模板) (编译注入) ***知识点*** //数据必须为Json数据(强调:jsonp数据不行,和json是两种数据,jsonp多了callback回调函数来包裹json数据) 遍 ...