前言

在本专栏的第一篇文章中有提过,后续内容包括:

  • Spark架构基础与运行原理
  • RDD编程
  • Spark SQL
  • Spark Streaming
  • Spark MLlib

⭐️目前为止,关于Spark中的RDD介绍已经告一段落,该部分的三篇文章分别为:


Zain Mei:Spark编程笔记(2)-RDD编程基础zhuanlan.zhihu.com


Zain Mei:Spark编程笔记(3)-键值对RDDzhuanlan.zhihu.com


Zain Mei:Spark编程笔记(4)-RDD数据读写zhuanlan.zhihu.com



接下来按照原定计划,开始介绍Spark SQL。

⭐️本文(RDD数据读写)目录如下:

前言
Spark SQL基础
DataFrame概述
DataFrame创建
DataFrame保存
反射机制推断RDD模式
编程方式定义RDD模式
Spark SQL读写 总结

Part1.Spark SQL基础

⭐️Hive库是一种基于Hadoop开发的数据仓库,相当于是SQL on Hadoop。

其内部将SQL转译为MapReduce作业。流程如下。




spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_json


Hive中SQL查询的MapReduce作业转化过程

Shark即Hive on Spark,为了实现与Hive兼容, Shark在 HiveQL方面重用了Hive中HiveQL的解析、逻辑执行计划翻译、 执行计划优化等逻辑,可以近似认为仅将物理执行计划从 MapReduce作业替换成了Spark作业,通过Hive的HiveQL解析,把HiveQL翻译成Spark上的RDD操作。

⭐️然而Shark导致了两个问题:

  1. 执行计划优化完全依赖于Hive,不方便添加新的优化策略;
  2. 因为Spark是线程级并行,而MapReduce是进程 级并行,因此,Spark在兼容Hive的实现上存在线程安 全问题,导致Shark不得不使用另外一套独立维护的打 了补丁的Hive源码分支。

2014年6月1日Shark项目和Spark SQL项目的主持人Reynold Xin宣布: 停止对Shark的开发,团队将所有资源放在Spark SQL项目上,至此, Shark的发展画上了句号,但也因此发展出两个分支:Spark SQL Hive on Spark。Spark SQL作为Spark 生态的一员继续发展,而不再受限于Hive,只是兼容Hive 。而Hive on Spark则致力于将Hive底层的MapReduce作业转化为Spark作业。

Spark SQL中新增了DataFrame(包含模式的RDD)。在Spark SQL中执行SQL语句,数据既可以来自RDD,也可以是Hive、 HDFS、Cassandra等外部数据源,还可以是JSON格式的数据 。关系型数据库之恶能通过select * from group by 等方法实现关系查询。Spark SQL可以同时融合多种数据源,且同时实现关系查询复杂分析算法

Part2.DataFrame

该部分包含4部分内容,分别为;

  • DataFrame概述
  • 创建DataFrame
  • 保存DataFrame
  • DataFrame常规操作

接下来分别看一下四部分的内容,在Spark中是如何实现的。

⭐️DataFrame概述。

DataFrame与RDD的区别DataFrame的推出,让Spark具备了处理大规模结构化数据的能力,不仅比原 有的RDD转化方式更加简单易用,而且获得了更高的计算性能 ;

Spark能够轻松实现从MySQL到DataFrame的转化 ,并且支持SQL查询;

RDD是分布式的Java对象的集合。但是,对象内部结构,对于RDD而言却是不可知的。DataFrame是一种以RDD为基础的分布式数据集,提供了详细的结构信息 。

⭐️创建DataFrame。

从Spark2.0以上版本开始,Spark使用全新的SparkSession接口替代 Spark1.6中的SQLContext及HiveContext接口来实现其对数据加载、转换、处理等功能。SparkSession实现了SQLContext及HiveContext所有功能。

SparkSession支持从不同的数据源加载数据,并把数据转换成 DataFrame,并且支持把DataFrame转换成SQLContext自身中的表, 然后使用SQL语句来操作数据。SparkSession亦提供了HiveQL以及其 他依赖于Hive的功能的支持。

创建一个SparkSession对象:


from pyspark import SparkContext,SparkConf
from pyspark.sql import SparkSession
spark = SparkSession.builder.config(conf = SparkConf()).getOrCreate()


实际上,在启动进入pyspark以后,pyspark就默认提供了一个SparkContext 对象(名称为sc)和一个SparkSession对象(名称为spark)。

在创建DataFrame时,可以使用spark.read操作。从不同类型的文件中加载数据创建DataFrame。


#读取文本文件people.txt创建DataFrame
spark.read.text("people.txt")
#读取people.json文件创建DataFrame。在读取本地文件或HDFS文件时,要注意给出正确的文件路径
。
spark.read.json("people.json")
#读取people.parquet文件创建DataFrame
spark.read.parquet("people.parquet")


或使用如下语句。


#读取文本文件people.json创建DataFrame
spark.read.format("text").load("people.txt")
#读取JSON文件people.json创建DataFrame
spark.read.format("json").load("people.json")
#读取Parquet文件people.parquet创建DataFrame
spark.read.format("parquet").load("people.parquet")


接下来用一个例子,实践DataFrame的创建 。

在“/usr/local/spark/examples/src/main/resources/”这个目录下,这个目录下有一个样例数据people.json。people.json文件的内容如下:


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_spark_02


读取文件。


>>> df = spark.read.json("file:///usr/local/spark/examples/src/main/resources/people.json")
>>> df.show()
+----+-------+
| age| name|
+----+-------+
|null|Michael|
| 30| Andy|
| 19| Justin|
+----+-------+


⭐️保存DataFrame。

可以使用spark.write操作,把一个DataFrame保存成不同格式的文件。

例如,把一个名称为df的DataFrame保存到不同格式文件中,方法如下:


df.write.text("people.txt")
df.write.json("people.json“)
df.write.parquet("people.parquet“)


也可以使用如下格式的语句:


df.write.format("text").save("people.txt")
df.write.format("json").save("people.json")
df.write.format ("parquet").save("people.parquet")


⭐️DataFrame常规操作。

可以执行一些常用的DataFrame操作。首先读取数据:


>>> df=spark.read.json(“people.json”)


接下来分别看几个常用的方法:

DataFrame.printSchema( )


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_json_03


DataFrame.select( )


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_spark_04


DataFrame.filter( )


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_json_05


DataFrame.groupBy( )


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_mapreduce编程初级实践_06


DataFrame.sort( )


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_SQL_07


Part3.RDD转换DataFrame

很多情况下数据是以RDD形式存在的,接下来介绍两种方法,对RDD文件和DataFrame文件进行相互转换。

⭐️反射机制

首先加载数据到内存中,生成RDD文件。数据为txt文件,如下图所示。


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_SQL_08


接下来进行编程操作。首先生成RDD文件,然后生成DataFrame文件。


>>> from pyspark.sql import Row
>>> people = spark.sparkContext.
... textFile("file:///usr/local/spark/examples/src/main/resources/people.txt").
... map(lambda line: line.split(",")).
... map(lambda p: Row(name=p[0], age=int(p[1])))
>>> schemaPeople = spark.createDataFrame(people)


这里需要注意,必须注册为临时表才能供下面的查询使用。


>>> schemaPeople.createOrReplaceTempView("people")
>>> personsDF = spark.sql("select name,age from people where age > 20")


上述查询后依旧是DataFrame。

DataFrame中的每个元素都是一行记录,包含name和age两个字段,分别用 p.name和p.age来获取值。既可以再的得到RDD文件。


>>> personsRDD=personsDF.rdd.map(lambda p:"Name: "+p.name+ ","+"Age:"+str(p.age))
>>> personsRDD.foreach(print)
Name: Michael,Age: 29
Name: Andy,Age: 30


上述代码运行逻辑如下图所示。


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_spark_09


⭐️编程方式定义

当无法提前获知数据结构时,就需要采用编程方式定义RDD模式。

比如,现在需要通过编程方式把people.txt加载进来生成 DataFrame,并完成SQL查询。具体逻辑如下图所示。下一个Part的示例中,也使用这种方法来生成DataFrame。


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_json_10

通过编程方式定义RDD模式的实现过程

代码示例如下,详细注释可见Pat4中最后的例子。


>>> from pyspark.sql.types import *
>>> from pyspark.sql import Row
#下面生成“表头”
>>> schemaString = "name age"
>>> fields = [StructField(field_name, StringType(), True) for field_name in
schemaString.split(" ")]
>>> schema = StructType(fields)
#下面生成“表中的记录”
>>> lines = spark.sparkContext.
... textFile("file:///usr/local/spark/examples/src/main/resources/people.txt")
>>> parts = lines.map(lambda x: x.split(","))
>>> people = parts.map(lambda p: Row(p[0], p[1].strip()))
#下面把“表头”和“表中的记录”拼装在一起
>>> schemaPeople = spark.createDataFrame(people, schema)


运行逻辑如下所示。


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_spark_11


Part4.Spark SQL读写Mysql

可以通过jdbc连接数据库,读取数据,并显示。具体的,只需要指定相应参数。

代码如下,详细解析见注释 :


>>> jdbcDF = spark.read.format("jdbc")  #通过jdbc连接Mysql数据库,以下均为参数
 .option("driver","com.mysql.jdbc.Driver")  #驱动程序
 .option("url", "jdbc:mysql://localhost:3306/spark")  #数据库地址以及具体的数据库
 .option("dbtable", "student")  #表明
 .option("user", "root")  #用户名 
 .option("password", "123456")  #密码
 .load() #启动连接过程
>>> jdbcDF.show() #DataFrame有show方法
+---+--------+------+---+
| id| name|gender|age|
+---+--------+------+---+
| 1| Xueqian| F| 23|
| 2|Weiliang| M| 24|
+---+--------+------+---+


假设现在有一个Mysql数据库中的表,名为spark.student,形如下图所示:


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_json_12


接下来要向spark.student表中插入两条记录 。则该过程可分为4步进行:

  1. 建立表头
  2. 生成Row对象
  3. 将表头与Row对象对应起来
  4. 写入数据库

接下来分别看一下相关代码和执行逻辑。

⭐️建立表头:


#!/usr/bin/env python3


⭐️生成Row对象:


#下面设置两条数据,表示两个学生的信息,parallelize得到包含两个元素的RDD,".map"不改变元素个数
studentRDD = spark.sparkContext.parallelize(["3 Rongcheng M 26","4 Guanhua M 27"]).map(lambda x:x.split(" "))
#下面创建Row对象,每个Row对象都是rowRDD中的一行,通过Row对象转化为DataFrame 
rowRDD = studentRDD.map(lambda p:Row(int(p[0].strip()), p[1].strip(),p[2].strip(), int(p[3].strip())))


前两个步骤代码运行逻辑如下图:


spark和mapreduce本地感知和任务调度算法的选择 mapreduce与spark_SQL_13


⭐️利用数据和表头生成DataFrame:


#建立起Row对象和模式之间的对应关系,也就是把数据(Row对象)和模式(表头)对应起来
studentDF = spark.createDataFrame(rowRDD, schema)


⭐️将DataFrame存入数据库中:


#写入数据库
prop = {}
prop['user'] = 'root' #用户名
prop['password'] = '123456' #密码
prop['driver'] = "com.mysql.jdbc.Driver" #驱动程序名称
#jdbc中的4个参数分别代表"数据库","表名","追加","相关信息全部写入"
studentDF.write.jdbc("jdbc:mysql://localhost:3306/spark",'student','append', prop)


总结

本篇文章记录了DataFrame的读写、常用方法,以及其与RDD之间的转化方式。下一篇文章开始介绍Spark streaming相关的内容。感谢阅读 。