背景

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. Java并发编程 Volatile关键字解析

    volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了 ...

  2. RabbitMQ消息队列之二:消费者和生产者

    在使用RabbitMQ之前,需要了解RabbitMQ的工作原理. RabbitMQ的工作原理 RabbitMQ是消息代理.从本质上说,它接受来自生产者的信息,并将它们传递给消费者.在两者之间,它可以根 ...

  3. Java线程池带图详解

    线程池作为Java中一个重要的知识点,看了很多文章,在此以Java自带的线程池为例,记录分析一下.本文参考了Java并发编程:线程池的使用.Java线程池---addWorker方法解析.线程池.Th ...

  4. 复选框demo

    本篇文章是关于复选框的,有2种形式:1.全选.反选由2个按钮实现:2.全选.反选由一个按钮实现. <!DOCTYPE html> <html> <head> < ...

  5. Apache下通过shell脚本提交网站404死链

    网站运营人员对于死链这个概念一定不陌生,网站的一些数据删除或页面改版等都容易制造死链,影响用户体验不说,过多的死链还会影响到网站的整体权重或排名. 百度站长平台提供的死链提交工具,可将网站存在的死链( ...

  6. Python NLP入门教程

    本文简要介绍Python自然语言处理(NLP),使用Python的NLTK库.NLTK是Python的自然语言处理工具包,在NLP领域中,最常使用的一个Python库. 什么是NLP? 简单来说,自然 ...

  7. iOS逆向环境以及常用命令行(逆向一)

    一.环境介绍 越狱环境:iPhone 5s iOS9.3.1 yueyu:~ root# uname -a Darwin yueyu 15.4.0 Darwin Kernel Version 15.4 ...

  8. 在Pycharm中使用jupyter笔记本

    在Pycharm中使用jupyter笔记本 我在Pycharm中使用jupyter笔记本,发现新的Jupyter更新中,增加了令牌. 随着创建的虚拟环境启动的所有设置,并将URL设置为127.0.0. ...

  9. linux下快速列出文件列表的方法

    前言 这两天碰到一个很棘手的问题,需要读取出ubuntu系统中某个目录下所有文件,由于服务器中存储的文件实在太多,导致此过程效率十分低下,动辄需要等待一个小时之久,还只是一个目录.于是如何快速获取文件 ...

  10. Spark版wordcount,并根据词频进行排序

    import org.apache.spark.{SparkConf, SparkContext}/** * Created by loushsh on 2017/10/9. */object Wor ...