Update(Stage4):sparksql:第1节 SparkSQL_使用场景_优化器_Dataset & 第2节 SparkSQL读写_hive_mysql_案例
SparkSQL是什么SparkSQL如何使用
1. SparkSQL 是什么
对于一件事的理解, 应该分为两个大部分, 第一, 它是什么, 第二, 它解决了什么问题
理解为什么会有
SparkSQL理解
SparkSQL所解决的问题, 以及它的使命
1.1. SparkSQL 的出现契机
理解 SparkSQL 是什么
历史前提
发展过程
重要性
数据分析的方式大致上可以划分为 SQL 和 命令式两种
- 命令式
-
在前面的
RDD部分, 非常明显可以感觉的到是命令式的, 主要特征是通过一个算子, 可以得到一个结果, 通过结果再进行后续计算.sc.textFile("...")
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.collect()- 命令式的优点
-
操作粒度更细, 能够控制数据的每一个处理环节
操作更明确, 步骤更清晰, 容易维护
支持非结构化数据的操作
- 命令式的缺点
-
需要一定的代码功底
写起来比较麻烦
- SQL
-
对于一些数据科学家, 要求他们为了做一个非常简单的查询, 写一大堆代码, 明显是一件非常残忍的事情, 所以
SQL on Hadoop是一个非常重要的方向.SELECT
name,
age,
school
FROM students
WHERE age > 10- SQL 的优点
-
表达非常清晰, 比如说这段
SQL明显就是为了查询三个字段, 又比如说这段SQL明显能看到是想查询年龄大于 10 岁的条目
- SQL 的缺点
-
想想一下 3 层嵌套的
SQL, 维护起来应该挺力不从心的吧试想一下, 如果使用
SQL来实现机器学习算法, 也挺为难的吧
SQL 擅长数据分析和通过简单的语法表示查询, 命令式操作适合过程式处理和算法性的处理. 在 Spark 出现之前, 对于结构化数据的查询和处理, 一个工具一向只能支持 SQL 或者命令式, 使用者被迫要使用多个工具来适应两种场景, 并且多个工具配合起来比较费劲.
而 Spark 出现了以后, 统一了两种数据处理范式, 是一种革新性的进步.
因为 SQL 是数据分析领域一个非常重要的范式, 所以 Spark 一直想要支持这种范式, 而伴随着一些决策失误, 这个过程其实还是非常曲折的

- Hive
-
- 解决的问题
-
Hive实现了SQL on Hadoop, 使用MapReduce执行任务简化了
MapReduce任务
- 新的问题
-
Hive的查询延迟比较高, 原因是使用MapReduce做调度
- Shark
-
- 解决的问题
-
Shark改写Hive的物理执行计划, 使用Spark作业代替MapReduce执行物理计划使用列式内存存储
以上两点使得
Shark的查询效率很高
- 新的问题
-
Shark重用了Hive的SQL解析, 逻辑计划生成以及优化, 所以其实可以认为Shark只是把Hive的物理执行替换为了Spark作业执行计划的生成严重依赖
Hive, 想要增加新的优化非常困难Hive使用MapReduce执行作业, 所以Hive是进程级别的并行, 而Spark是线程级别的并行, 所以Hive中很多线程不安全的代码不适用于Spark
由于以上问题,
Shark维护了Hive的一个分支, 并且无法合并进主线, 难以为继 SparkSQL-
- 解决的问题
-
Spark SQL使用Hive解析SQL生成AST语法树, 将其后的逻辑计划生成, 优化, 物理计划都自己完成, 而不依赖Hive执行计划和优化交给优化器
Catalyst内建了一套简单的
SQL解析器, 可以不使用HQL, 此外, 还引入和DataFrame这样的DSL API, 完全可以不依赖任何Hive的组件Shark只能查询文件,Spark SQL可以直接降查询作用于RDD, 这一点是一个大进步
- 新的问题
-
对于初期版本的
SparkSQL, 依然有挺多问题, 例如只能支持SQL的使用, 不能很好的兼容命令式, 入口不够统一等
Dataset-
SparkSQL在 2.0 时代, 增加了一个新的API, 叫做Dataset,Dataset统一和结合了SQL的访问和命令式API的使用, 这是一个划时代的进步在
Dataset中可以轻易的做到使用SQL查询并且筛选数据, 然后使用命令式API进行探索式分析
|
重要性
![]()
|
SparkSQL 是什么SparkSQL 是一个为了支持 SQL 而设计的工具, 但同时也支持命令式的 API
1.2. SparkSQL 的适用场景
理解 SparkSQL 的适用场景
| 定义 | 特点 | 举例 | |
|---|---|---|---|
|
结构化数据 |
有固定的 |
有预定义的 |
关系型数据库的表 |
|
半结构化数据 |
没有固定的 |
没有固定的 |
指一些有结构的文件格式, 例如 |
|
非结构化数据 |
没有固定 |
没有固定 |
指文档图片之类的格式 |
- 结构化数据
-
一般指数据有固定的
Schema, 例如在用户表中,name字段是String型, 那么每一条数据的name字段值都可以当作String来使用+----+--------------+---------------------------+-------+---------+
| id | name | url | alexa | country |
+----+--------------+---------------------------+-------+---------+
| 1 | Google | https://www.google.cm/ | 1 | USA |
| 2 | 淘宝 | https://www.taobao.com/ | 13 | CN |
| 3 | 菜鸟教程 | http://www.runoob.com/ | 4689 | CN |
| 4 | 微博 | http://weibo.com/ | 20 | CN |
| 5 | Facebook | https://www.facebook.com/ | 3 | USA |
+----+--------------+---------------------------+-------+---------+ - 半结构化数据
-
一般指的是数据没有固定的
Schema, 但是数据本身是有结构的{
"firstName": "John",
"lastName": "Smith",
"age": 25,
"phoneNumber":
[
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "fax",
"number": "646 555-4567"
}
]
}- 没有固定
Schema -
指的是半结构化数据是没有固定的
Schema的, 可以理解为没有显式指定Schema
比如说一个用户信息的JSON文件, 第一条数据的phone_num有可能是String, 第二条数据虽说应该也是String, 但是如果硬要指定为BigInt, 也是有可能的
因为没有指定Schema, 没有显式的强制的约束 - 有结构
-
虽说半结构化数据是没有显式指定
Schema的, 也没有约束, 但是半结构化数据本身是有有隐式的结构的, 也就是数据自身可以描述自身
例如JSON文件, 其中的某一条数据是有字段这个概念的, 每个字段也有类型的概念, 所以说JSON是可以描述自身的, 也就是数据本身携带有元信息
- 没有固定
SparkSQL处理什么数据的问题?-
Spark的RDD主要用于处理 非结构化数据 和 半结构化数据SparkSQL主要用于处理 结构化数据
SparkSQL相较于RDD的优势在哪?-
SparkSQL提供了更好的外部数据源读写支持因为大部分外部数据源是有结构化的, 需要在
RDD之外有一个新的解决方案, 来整合这些结构化数据源
SparkSQL提供了直接访问列的能力因为
SparkSQL主要用做于处理结构化数据, 所以其提供的API具有一些普通数据库的能力
SparkSQL 适用于什么场景?SparkSQL 适用于处理结构化数据的场景
SparkSQL是一个即支持SQL又支持命令式数据处理的工具SparkSQL的主要适用场景是处理结构化数据
2. SparkSQL 初体验
了解
SparkSQL的API由哪些部分组成
2.3. RDD 版本的 WordCount
val config = new SparkConf().setAppName("ip_ana").setMaster("local[6]")
val sc = new SparkContext(config)
sc.textFile("hdfs://node01:8020/dataset/wordcount.txt")
.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
.collect
RDD版本的代码有一个非常明显的特点, 就是它所处理的数据是基本类型的, 在算子中对整个数据进行处理
2.2. 命令式 API 的入门案例
case class People(name: String, age: Int)
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate()
import spark.implicits._
val peopleRDD: RDD[People] = spark.sparkContext.parallelize(Seq(People("zhangsan", 9), People("lisi", 15)))
val peopleDS: Dataset[People] = peopleRDD.toDS()
val teenagers: Dataset[String] = peopleDS.where('age > 10)
.where('age < 20)
.select('name)
.as[String]
/*
+----+
|name|
+----+
|lisi|
+----+
*/
teenagers.show()
| SparkSQL 中有一个新的入口点, 叫做 SparkSession | |
| SparkSQL 中有一个新的类型叫做 Dataset | |
| SparkSQL 有能力直接通过字段名访问数据集, 说明 SparkSQL 的 API 中是携带 Schema 信息的 |
SparkContext作为RDD的创建者和入口, 其主要作用有如下两点-
创建
RDD, 主要是通过读取文件创建RDD监控和调度任务, 包含了一系列组件, 例如
DAGScheduler,TaskSheduler
- 为什么无法使用
SparkContext作为SparkSQL的入口? -
SparkContext在读取文件的时候, 是不包含Schema信息的, 因为读取出来的是RDDSparkContext在整合数据源如Cassandra,JSON,Parquet等的时候是不灵活的, 而DataFrame和Dataset一开始的设计目标就是要支持更多的数据源SparkContext的调度方式是直接调度RDD, 但是一般情况下针对结构化数据的访问, 会先通过优化器优化一下
所以 SparkContext 确实已经不适合作为 SparkSQL 的入口, 所以刚开始的时候 Spark 团队为 SparkSQL 设计了两个入口点, 一个是 SQLContext 对应 Spark 标准的 SQL 执行, 另外一个是 HiveContext 对应 HiveSQL 的执行和 Hive 的支持.
在 Spark 2.0 的时候, 为了解决入口点不统一的问题, 创建了一个新的入口点 SparkSession, 作为整个 Spark 生态工具的统一入口点, 包括了 SQLContext, HiveContext, SparkContext 等组件的功能
- 新的入口应该有什么特性?
-
能够整合
SQLContext,HiveContext,SparkContext,StreamingContext等不同的入口点为了支持更多的数据源, 应该完善读取和写入体系
同时对于原来的入口点也不能放弃, 要向下兼容

SparkSQL 最大的特点就是它针对于结构化数据设计, 所以 SparkSQL 应该是能支持针对某一个字段的访问的, 而这种访问方式有一个前提, 就是 SparkSQL 的数据集中, 要 包含结构化信息, 也就是俗称的 Schema
而 SparkSQL 对外提供的 API 有两类, 一类是直接执行 SQL, 另外一类就是命令式. SparkSQL 提供的命令式 API 就是 DataFrame 和 Dataset, 暂时也可以认为 DataFrame 就是 Dataset, 只是在不同的 API 中返回的是 Dataset 的不同表现形式
// RDD
rdd.map { case Person(id, name, age) => (age, 1) }
.reduceByKey {case ((age, count), (totalAge, totalCount)) => (age, count + totalCount)}
// DataFrame
df.groupBy("age").count("age")
通过上面的代码, 可以清晰的看到, SparkSQL 的命令式操作相比于 RDD 来说, 可以直接通过 Schema 信息来访问其中某个字段, 非常的方便
2.2. SQL 版本 WordCount
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate()
import spark.implicits._
val peopleRDD: RDD[People] = spark.sparkContext.parallelize(Seq(People("zhangsan", 9), People("lisi", 15)))
val peopleDS: Dataset[People] = peopleRDD.toDS()
peopleDS.createOrReplaceTempView("people")
val teenagers: DataFrame = spark.sql("select name from people where age > 10 and age < 20")
/*
+----+
|name|
+----+
|lisi|
+----+
*/
teenagers.show()
以往使用 SQL 肯定是要有一个表的, 在 Spark 中, 并不存在表的概念, 但是有一个近似的概念, 叫做 DataFrame, 所以一般情况下要先通过 DataFrame 或者 Dataset 注册一张临时表, 然后使用 SQL 操作这张临时表
SparkSQL 提供了 SQL 和 命令式 API 两种不同的访问结构化数据的形式, 并且它们之间可以无缝的衔接
命令式 API 由一个叫做 Dataset 的组件提供, 其还有一个变形, 叫做 DataFrame
3. [扩展] Catalyst 优化器
理解
SparkSQL和以RDD为代表的SparkCore最大的区别理解优化器的运行原理和作用
3.1. RDD 和 SparkSQL 运行时的区别
RDD的运行流程-

- 大致运行步骤
-
先将
RDD解析为由Stage组成的DAG, 后将Stage转为Task直接运行 - 问题
-
任务会按照代码所示运行, 依赖开发者的优化, 开发者的会在很大程度上影响运行效率
- 解决办法
-
创建一个组件, 帮助开发者修改和优化代码, 但是这在
RDD上是无法实现的
- 为什么
RDD无法自我优化? -
RDD没有Schema信息RDD可以同时处理结构化和非结构化的数据
SparkSQL提供了什么?-

和
RDD不同,SparkSQL的Dataset和SQL并不是直接生成计划交给集群执行, 而是经过了一个叫做Catalyst的优化器, 这个优化器能够自动帮助开发者优化代码也就是说, 在
SparkSQL中, 开发者的代码即使不够优化, 也会被优化为相对较好的形式去执行- 为什么
SparkSQL提供了这种能力? -
首先,
SparkSQL大部分情况用于处理结构化数据和半结构化数据, 所以SparkSQL可以获知数据的Schema, 从而根据其Schema来进行优化
- 为什么
3.2. Catalyst
|
为了解决过多依赖 ![]()
|

- Step 1 : 解析
SQL, 并且生成AST(抽象语法树) -

- Step 2 : 在
AST中加入元数据信息, 做这一步主要是为了一些优化, 例如col = col这样的条件, 下图是一个简略图, 便于理解 -

score.id → id#1#L为score.id生成id为 1, 类型是Longscore.math_score → math_score#2#L为score.math_score生成id为 2, 类型为Longpeople.id → id#3#L为people.id生成id为 3, 类型为Longpeople.age → age#4#L为people.age生成id为 4, 类型为Long
- Step 3 : 对已经加入元数据的
AST, 输入优化器, 进行优化, 从两种常见的优化开始, 简单介绍 -

谓词下推
Predicate Pushdown, 将Filter这种可以减小数据集的操作下推, 放在Scan的位置, 这样可以减少操作时候的数据量

列值裁剪
Column Pruning, 在谓词下推后,people表之上的操作只用到了id列, 所以可以把其它列裁剪掉, 这样可以减少处理的数据量, 从而优化处理速度
还有其余很多优化点, 大概一共有一二百种, 随着
SparkSQL的发展, 还会越来越多, 感兴趣的同学可以继续通过源码了解, 源码在org.apache.spark.sql.catalyst.optimizer.Optimizer
- Step 4 : 上面的过程生成的
AST其实最终还没办法直接运行, 这个AST叫做逻辑计划, 结束后, 需要生成物理计划, 从而生成RDD来运行 -
在生成`物理计划`的时候, 会经过`成本模型`对整棵树再次执行优化, 选择一个更好的计划
在生成`物理计划`以后, 因为考虑到性能, 所以会使用代码生成, 在机器中运行
|
SparkSQL 和 RDD 不同的主要点是在于其所操作的数据是结构化的, 提供了对数据更强的感知和分析能力, 能够对代码进行更深层的优化, 而这种能力是由一个叫做 Catalyst 的优化器所提供的
Catalyst 的主要运作原理是分为三步, 先对 SQL 或者 Dataset 的代码解析, 生成逻辑计划, 后对逻辑计划进行优化, 再生成物理计划, 最后生成代码到集群中以 RDD 的形式运行
4. Dataset 的特点
理解
Dataset是什么理解
Dataset的特性
Dataset是什么?-
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() import spark.implicits._ val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15)))
// 方式1: 通过对象来处理
dataset.filter(item => item.age > 10).show()
// 方式2: 通过字段来处理
dataset.filter('age > 10).show()
// 方式3: 通过类似SQL的表达式来处理
dataset.filter("age > 10").show()- 问题1:
People是什么? -
People是一个强类型的类 - 问题2: 这个
Dataset中是结构化的数据吗? -
非常明显是的, 因为
People对象中有结构信息, 例如字段名和字段类型 - 问题3: 这个
Dataset能够使用类似SQL这样声明式结构化查询语句的形式来查询吗? -
当然可以, 已经演示过了
- 问题4:
Dataset是什么? -
Dataset是一个强类型, 并且类型安全的数据容器, 并且提供了结构化查询API和类似RDD一样的命令式API
- 问题1:
- 即使使用
Dataset的命令式API, 执行计划也依然会被优化 -
Dataset具有RDD的方便, 同时也具有DataFrame的性能优势, 并且Dataset还是强类型的, 能做到类型安全.scala> spark.range(1).filter('id === 0).explain(true) == Parsed Logical Plan ==
'Filter ('id = 0)
+- Range (0, 1, splits=8) == Analyzed Logical Plan ==
id: bigint
Filter (id#51L = cast(0 as bigint))
+- Range (0, 1, splits=8) == Optimized Logical Plan ==
Filter (id#51L = 0)
+- Range (0, 1, splits=8) == Physical Plan ==
*Filter (id#51L = 0)
+- *Range (0, 1, splits=8) Dataset的底层是什么?-
Dataset最底层处理的是对象的序列化形式, 通过查看Dataset生成的物理执行计划, 也就是最终所处理的RDD, 就可以判定Dataset底层处理的是什么形式的数据val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15)))
val internalRDD: RDD[InternalRow] = dataset.queryExecution.toRdddataset.queryExecution.toRdd这个API可以看到Dataset底层执行的RDD, 这个RDD中的范型是InternalRow,InternalRow又称之为Catalyst Row, 是Dataset底层的数据结构, 也就是说, 无论Dataset的范型是什么, 无论是Dataset[Person]还是其它的, 其最底层进行处理的数据结构都是InternalRow所以,
Dataset的范型对象在执行之前, 需要通过Encoder转换为InternalRow, 在输入之前, 需要把InternalRow通过Decoder转换为范型对象
- 可以获取
Dataset对应的RDD表示 -
在
Dataset中, 可以使用一个属性rdd来得到它的RDD表示, 例如Dataset[T] → RDD[T]val dataset: Dataset[People] = spark.createDataset(Seq(People("zhangsan", 9), People("lisi", 15))) /*
(2) MapPartitionsRDD[3] at rdd at Testing.scala:159 []
| MapPartitionsRDD[2] at rdd at Testing.scala:159 []
| MapPartitionsRDD[1] at rdd at Testing.scala:159 []
| ParallelCollectionRDD[0] at rdd at Testing.scala:159 []
*/ println(dataset.rdd.toDebugString) // 这段代码的执行计划为什么多了两个步骤? /*
(2) MapPartitionsRDD[5] at toRdd at Testing.scala:160 []
| ParallelCollectionRDD[4] at toRdd at Testing.scala:160 []
*/ println(dataset.queryExecution.toRdd.toDebugString)使用 Dataset.rdd将Dataset转为RDD的形式Dataset的执行计划底层的RDD可以看到
(1)对比(2)对了两个步骤, 这两个步骤的本质就是将Dataset底层的InternalRow转为RDD中的对象形式, 这个操作还是会有点重的, 所以慎重使用rdd属性来转换Dataset为RDD
Dataset是一个新的Spark组件, 其底层还是RDDDataset提供了访问对象中某个特定字段的能力, 不用像RDD一样每次都要针对整个对象做操作Dataset和RDD不同, 如果想把Dataset[T]转为RDD[T], 则需要对Dataset底层的InternalRow做转换, 是一个比价重量级的操作
5. DataFrame 的作用和常见操作
理解
DataFrame是什么理解
DataFrame的常见操作
DataFrame是什么?-
DataFrame是SparkSQL中一个表示关系型数据库中表的函数式抽象, 其作用是让Spark处理大规模结构化数据的时候更加容易. 一般DataFrame可以处理结构化的数据, 或者是半结构化的数据, 因为这两类数据中都可以获取到Schema信息. 也就是说DataFrame中有Schema信息, 可以像操作表一样操作DataFrame.
DataFrame由两部分构成, 一是row的集合, 每个row对象表示一个行, 二是描述DataFrame结构的Schema.
DataFrame支持SQL中常见的操作, 例如:select,filter,join,group,sort,join等val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() import spark.implicits._ val peopleDF: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF() /*
+---+-----+
|age|count|
+---+-----+
| 15| 2|
+---+-----+
*/
peopleDF.groupBy('age)
.count()
.show() - 通过隐式转换创建
DataFrame -
这种方式本质上是使用
SparkSession中的隐式转换来进行的val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() // 必须要导入隐式转换
// 注意: spark 在此处不是包, 而是 SparkSession 对象
import spark.implicits._ val peopleDF: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()
根据源码可以知道,
toDF方法可以在RDD和Seq中使用通过集合创建
DataFrame的时候, 集合中不仅可以包含样例类, 也可以只有普通数据类型, 后通过指定列名来创建val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() import spark.implicits._ val df1: DataFrame = Seq("nihao", "hello").toDF("text") /*
+-----+
| text|
+-----+
|nihao|
|hello|
+-----+
*/
df1.show() val df2: DataFrame = Seq(("a", 1), ("b", 1)).toDF("word", "count") /*
+----+-----+
|word|count|
+----+-----+
| a| 1|
| b| 1|
+----+-----+
*/
df2.show() - 通过外部集合创建
DataFrame -
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() val df = spark.read
.option("header", true)
.csv("dataset/BeijingPM20100101_20151231.csv")
df.show(10)
df.printSchema()不仅可以从
csv文件创建DataFrame, 还可以从Table,JSON,Parquet等中创建DataFrame, 后续会有单独的章节来介绍 - 在
DataFrame上可以使用的常规操作 -
需求: 查看每个月的统计数量
- Step 1: 首先可以打印
DataFrame的Schema, 查看其中所包含的列, 以及列的类型 -
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() val df = spark.read
.option("header", true)
.csv("dataset/BeijingPM20100101_20151231.csv") df.printSchema() - Step 2: 对于大部分计算来说, 可能不会使用所有的列, 所以可以选择其中某些重要的列
-
... df.select('year, 'month, 'PM_Dongsi) - Step 3: 可以针对某些列进行分组, 后对每组数据通过函数做聚合
-
... df.select('year, 'month, 'PM_Dongsi)
.where('PM_Dongsi =!= "Na")
.groupBy('year, 'month)
.count()
.show()
- Step 1: 首先可以打印
- 使用
SQL操作DataFrame
使用 SQL 来操作某个 DataFrame 的话, SQL 中必须要有一个 from 子句, 所以需要先将 DataFrame 注册为一张临时表
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate()
val df = spark.read
.option("header", true)
.csv("dataset/BeijingPM20100101_20151231.csv")
df.createOrReplaceTempView("temp_table")
spark.sql("select year, month, count(*) from temp_table where PM_Dongsi != 'NA' group by year, month")
.show()
DataFrame是一个类似于关系型数据库表的函数式组件DataFrame一般处理结构化数据和半结构化数据DataFrame具有数据对象的 Schema 信息可以使用命令式的
API操作DataFrame, 同时也可以使用SQL操作DataFrameDataFrame可以由一个已经存在的集合直接创建, 也可以读取外部的数据源来创建
6. Dataset 和 DataFrame 的异同
理解
Dataset和DataFrame之间的关系
DataFrame就是Dataset-
根据前面的内容, 可以得到如下信息
Dataset中可以使用列来访问数据,DataFrame也可以Dataset的执行是优化的,DataFrame也是Dataset具有命令式API, 同时也可以使用SQL来访问,DataFrame也可以使用这两种不同的方式访问
所以这件事就比较蹊跷了, 两个这么相近的东西为什么会同时出现在
SparkSQL中呢?
确实, 这两个组件是同一个东西,
DataFrame是Dataset的一种特殊情况, 也就是说DataFrame是Dataset[Row]的别名 DataFrame和Dataset所表达的语义不同-
第一点:
DataFrame表达的含义是一个支持函数式操作的表, 而Dataset表达是是一个类似RDD的东西,Dataset可以处理任何对象- 第二点:
DataFrame中所存放的是Row对象, 而Dataset中可以存放任何类型的对象 -
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() import spark.implicits._ val df: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF() val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()DataFrame 就是 Dataset[Row] Dataset 的范型可以是任意类型 - 第三点:
DataFrame的操作方式和Dataset是一样的, 但是对于强类型操作而言, 它们处理的类型不同 -
DataFrame在进行强类型操作时候, 例如map算子, 其所处理的数据类型永远是Rowdf.map( (row: Row) => Row(row.get(0), row.getAs[Int](1) * 10) )(RowEncoder.apply(df.schema)).show()但是对于
Dataset来讲, 其中是什么类型, 它就处理什么类型ds.map( (item: People) => People(item.name, item.age * 10) ).show() - 第三点:
DataFrame只能做到运行时类型检查,Dataset能做到编译和运行时都有类型检查 -
DataFrame中存放的数据以Row表示, 一个Row代表一行数据, 这和关系型数据库类似DataFrame在进行map等操作的时候,DataFrame不能直接使用Person这样的Scala对象, 所以无法做到编译时检查Dataset表示的具体的某一类对象, 例如Person, 所以再进行map等操作的时候, 传入的是具体的某个Scala对象, 如果调用错了方法, 编译时就会被检查出来
val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
ds.map(person => person.hello)这行代码明显报错, 无法通过编译
- 第二点:
Row是什么?-
Row对象表示的是一个行Row的操作类似于Scala中的Map数据类型// 一个对象就是一个对象
val p = People(name = "zhangsan", age = 10) // 同样一个对象, 还可以通过一个 Row 对象来表示
val row = Row("zhangsan", 10) // 获取 Row 中的内容
println(row.get(1))
println(row(1)) // 获取时可以指定类型
println(row.getAs[Int](1)) // 同时 Row 也是一个样例类, 可以进行 match
row match {
case Row(name, age) => println(name, age)
} DataFrame和Dataset之间可以非常简单的相互转换-
val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() import spark.implicits._ val df: DataFrame = Seq(People("zhangsan", 15), People("lisi", 15)).toDF()
val ds_fdf: Dataset[People] = df.as[People] val ds: Dataset[People] = Seq(People("zhangsan", 15), People("lisi", 15)).toDS()
val df_fds: DataFrame = ds.toDF()
DataFrame就是Dataset, 他们的方式是一样的, 也都支持API和SQL两种操作方式DataFrame只能通过表达式的形式, 或者列的形式来访问数据, 只有Dataset支持针对于整个对象的操作DataFrame中的数据表示为Row, 是一个行的概念
7. 数据读写
理解外部数据源的访问框架
掌握常见的数据源读写方式
7.1. 初识 DataFrameReader
理解
DataFrameReader的整体结构和组成
SparkSQL 的一个非常重要的目标就是完善数据读取, 所以 SparkSQL 中增加了一个新的框架, 专门用于读取外部数据源, 叫做 DataFrameReader
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrameReader
val spark: SparkSession = ...
val reader: DataFrameReader = spark.read
DataFrameReader 由如下几个组件组成
| 组件 | 解释 |
|---|---|
|
|
结构信息, 因为 |
|
|
连接外部数据源的参数, 例如 |
|
|
外部数据源的格式, 例如 |
DataFrameReader 有两种访问方式, 一种是使用 load 方法加载, 使用 format 指定加载格式, 还有一种是使用封装方法, 类似 csv, json, jdbc 等
import org.apache.spark.sql.SparkSession
import org.apache.spark.sql.DataFrame
val spark: SparkSession = ...
// 使用 load 方法
val fromLoad: DataFrame = spark
.read
.format("csv")
.option("header", true)
.option("inferSchema", true)
.load("dataset/BeijingPM20100101_20151231.csv")
// Using format-specific load operator
val fromCSV: DataFrame = spark
.read
.option("header", true)
.option("inferSchema", true)
.csv("dataset/BeijingPM20100101_20151231.csv")
但是其实这两种方式本质上一样, 因为类似 csv 这样的方式只是 load 的封装

|
如果使用 也就是说, |
使用
spark.read可以获取 SparkSQL 中的外部数据源访问框架DataFrameReaderDataFrameReader有三个组件format,schema,optionDataFrameReader有两种使用方式, 一种是使用load加format指定格式, 还有一种是使用封装方法csv,json等
7.2. 初识 DataFrameWriter
理解
DataFrameWriter的结构
对于 ETL 来说, 数据保存和数据读取一样重要, 所以 SparkSQL 中增加了一个新的数据写入框架, 叫做 DataFrameWriter
val spark: SparkSession = ...
val df = spark.read
.option("header", true)
.csv("dataset/BeijingPM20100101_20151231.csv")
val writer: DataFrameWriter[Row] = df.write
DataFrameWriter 中由如下几个部分组成
| 组件 | 解释 |
|---|---|
|
|
写入目标, 文件格式等, 通过 |
|
|
写入模式, 例如一张表已经存在, 如果通过 |
|
|
外部参数, 例如 |
|
|
类似 |
|
|
类似 |
|
|
用于排序的列, 通过 |
mode 指定了写入模式, 例如覆盖原数据集, 或者向原数据集合中尾部添加等
Scala 对象表示 |
字符串表示 | 解释 |
|---|---|---|
|
|
|
将 |
|
|
|
将 |
|
|
|
将 |
|
|
|
将 |
DataFrameWriter 也有两种使用方式, 一种是使用 format 配合 save, 还有一种是使用封装方法, 例如 csv, json, saveAsTable 等
val spark: SparkSession = ...
val df = spark.read
.option("header", true)
.csv("dataset/BeijingPM20100101_20151231.csv")
// 使用 save 保存, 使用 format 设置文件格式
df.write.format("json").save("dataset/beijingPM")
// 使用 json 保存, 因为方法是 json, 所以隐含的 format 是 json
df.write.json("dataset/beijingPM1")
|
默认没有指定 |
类似
DataFrameReader,Writer中也有format,options, 另外schema是包含在DataFrame中的DataFrameWriter中还有一个很重要的概念叫做mode, 指定写入模式, 如果目标集合已经存在时的行为DataFrameWriter可以将数据保存到Hive表中, 所以也可以指定分区和分桶信息
7.3. 读写 Parquet 格式文件
理解
Spark读写Parquet文件的语法理解
Spark读写Parquet文件的时候对于分区的处理
- 什么时候会用到
Parquet? -

在
ETL中,Spark经常扮演T的职务, 也就是进行数据清洗和数据转换.为了能够保存比较复杂的数据, 并且保证性能和压缩率, 通常使用
Parquet是一个比较不错的选择.所以外部系统收集过来的数据, 有可能会使用
Parquet, 而Spark进行读取和转换的时候, 就需要支持对Parquet格式的文件的支持. - 使用代码读写
Parquet文件 -
默认不指定
format的时候, 默认就是读写Parquet格式的文件val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() val df = spark.read
.option("header", value = true)
.csv("dataset/911.csv") // 保存 Parquet 文件
df.write.mode("override").save("dataset/911.parquet") // 读取 Parquet 文件
val dfFromParquet = spark.read.parquet("dataset/911.parquet")
dfFromParquet.createOrReplaceTempView("911") spark.sql("select * from 911 where zip > 19000 and zip < 19400").show() - 写入
Parquet的时候可以指定分区 -
Spark在写入文件的时候是支持分区的, 可以像Hive一样设置某个列为分区列val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() // 从 CSV 中读取内容
val dfFromParquet = spark.read.option("header", value = true).csv("dataset/BeijingPM20100101_20151231.csv") // 保存为 Parquet 格式文件, 不指定 format 默认就是 Parquet
dfFromParquet.write.partitionBy("year", "month").save("dataset/beijing_pm")
|
这个地方指的分区是类似 |
- 分区发现
-
在读取常见文件格式的时候,
Spark会自动的进行分区发现, 分区自动发现的时候, 会将文件名中的分区信息当作一列. 例如 如果按照性别分区, 那么一般会生成两个文件夹gender=male和gender=female, 那么在使用Spark读取的时候, 会自动发现这个分区信息, 并且当作列放入创建的DataFrame中使用代码证明这件事可以有两个步骤, 第一步先读取某个分区的单独一个文件并打印其
Schema信息, 第二步读取整个数据集所有分区并打印Schema信息, 和第一步做比较就可以确定val spark = ... val partDF = spark.read.load("dataset/beijing_pm/year=2010/month=1")
partDF.printSchema()把分区的数据集中的某一个区单做一整个数据集读取, 没有分区信息, 自然也不会进行分区发现 
val df = spark.read.load("dataset/beijing_pm")
df.printSchema()此处读取的是整个数据集, 会进行分区发现, DataFrame 中会包含分去列 
| 配置 | 默认值 | 含义 |
|---|---|---|
|
|
|
一些其他 |
|
|
|
一些其他 |
|
|
|
打开 Parquet 元数据的缓存, 可以加快查询静态数据 |
|
|
|
压缩方式, 可选 |
|
|
|
当为 true 时, Parquet 数据源会合并从所有数据文件收集的 Schemas 和数据, 因为这个操作开销比较大, 所以默认关闭 |
|
|
|
如果为 |
Spark不指定format的时候默认就是按照Parquet的格式解析文件Spark在读取Parquet文件的时候会自动的发现Parquet的分区和分区字段Spark在写入Parquet文件的时候如果设置了分区字段, 会自动的按照分区存储
7.4. 读写 JSON 格式文件
理解
JSON的使用场景能够使用
Spark读取处理JSON格式文件
- 什么时候会用到
JSON? -

在
ETL中,Spark经常扮演T的职务, 也就是进行数据清洗和数据转换.在业务系统中,
JSON是一个非常常见的数据格式, 在前后端交互的时候也往往会使用JSON, 所以从业务系统获取的数据很大可能性是使用JSON格式, 所以就需要Spark能够支持 JSON 格式文件的读取 - 读写
JSON文件 -
将要
Dataset保存为JSON格式的文件比较简单, 是DataFrameWriter的一个常规使用val spark: SparkSession = new sql.SparkSession.Builder()
.appName("hello")
.master("local[6]")
.getOrCreate() val dfFromParquet = spark.read.load("dataset/beijing_pm") // 将 DataFrame 保存为 JSON 格式的文件
dfFromParquet.repartition(1)
.write.format("json")
.save("dataset/beijing_pm_json")如果不重新分区, 则会为 DataFrame底层的RDD的每个分区生成一个文件, 为了保持只有一个输出文件, 所以重新分区保存为
JSON格式的文件有一个细节需要注意, 这个JSON格式的文件中, 每一行是一个独立的JSON, 但是整个文件并不只是一个JSON字符串, 所以这种文件格式很多时候被成为JSON Line文件, 有时候后缀名也会变为jsonlbeijing_pm.jsonl{"day":"1","hour":"0","season":"1","year":2013,"month":3}
{"day":"1","hour":"1","season":"1","year":2013,"month":3}
{"day":"1","hour":"2","season":"1","year":2013,"month":3}也可以通过
DataFrameReader读取一个JSON Line文件val spark: SparkSession = ... val dfFromJSON = spark.read.json("dataset/beijing_pm_json")
dfFromJSON.show()JSON格式的文件是有结构信息的, 也就是JSON中的字段是有类型的, 例如"name": "zhangsan"这样由双引号包裹的Value, 就是字符串类型, 而"age": 10这种没有双引号包裹的就是数字类型, 当然, 也可以是布尔型"has_wife": trueSpark读取JSON Line文件的时候, 会自动的推断类型信息val spark: SparkSession = ... val dfFromJSON = spark.read.json("dataset/beijing_pm_json") dfFromJSON.printSchema()
Spark可以从一个保存了JSON格式字符串的Dataset[String]中读取JSON信息, 转为DataFrame-
这种情况其实还是比较常见的, 例如如下的流程

假设业务系统通过
Kafka将数据流转进入大数据平台, 这个时候可能需要使用RDD或者Dataset来读取其中的内容, 这个时候一条数据就是一个JSON格式的字符串, 如何将其转为DataFrame或者Dataset[Object]这样具有Schema的数据集呢? 使用如下代码就可以val spark: SparkSession = ... import spark.implicits._ val peopleDataset = spark.createDataset(
"""{"name":"Yin","address":{"city":"Columbus","state":"Ohio"}}""" :: Nil) spark.read.json(peopleDataset).show()
JSON通常用于系统间的交互,Spark经常要读取JSON格式文件, 处理, 放在另外一处使用
DataFrameReader和DataFrameWriter可以轻易的读取和写入JSON, 并且会自动处理数据类型信息
7.5. 访问 Hive
整合
SparkSQL和Hive, 使用Hive的MetaStore元信息库使用
SparkSQL查询Hive表案例, 使用常见
HiveSQL写入内容到
Hive表
7.5.1. SparkSQL 整合 Hive
开启
Hive的MetaStore独立进程整合
SparkSQL和Hive的MetaStore
和一个文件格式不同, Hive 是一个外部的数据存储和查询引擎, 所以如果 Spark 要访问 Hive 的话, 就需要先整合 Hive
- 整合什么 ?
-
如果要讨论
SparkSQL如何和Hive进行整合, 首要考虑的事应该是Hive有什么, 有什么就整合什么就可以MetaStore, 元数据存储SparkSQL内置的有一个MetaStore, 通过嵌入式数据库Derby保存元信息, 但是对于生产环境来说, 还是应该使用Hive的MetaStore, 一是更成熟, 功能更强, 二是可以使用Hive的元信息查询引擎
SparkSQL内置了HiveSQL的支持, 所以无需整合
- 为什么要开启
Hive的MetaStore -
Hive的MetaStore是一个Hive的组件, 一个Hive提供的程序, 用以保存和访问表的元数据, 整个Hive的结构大致如下
由上图可知道, 其实
Hive中主要的组件就三个,HiveServer2负责接受外部系统的查询请求, 例如JDBC,HiveServer2接收到查询请求后, 交给Driver处理,Driver会首先去询问MetaStore表在哪存, 后Driver程序通过MR程序来访问HDFS从而获取结果返回给查询请求者而
Hive的MetaStore对SparkSQL的意义非常重大, 如果SparkSQL可以直接访问Hive的MetaStore, 则理论上可以做到和Hive一样的事情, 例如通过Hive表查询数据而 Hive 的 MetaStore 的运行模式有三种
内嵌
Derby数据库模式这种模式不必说了, 自然是在测试的时候使用, 生产环境不太可能使用嵌入式数据库, 一是不稳定, 二是这个
Derby是单连接的, 不支持并发Local模式Local和Remote都是访问MySQL数据库作为存储元数据的地方, 但是Local模式的MetaStore没有独立进程, 依附于HiveServer2的进程Remote模式和
Loca模式一样, 访问MySQL数据库存放元数据, 但是Remote的MetaStore运行在独立的进程中
我们显然要选择
Remote模式, 因为要让其独立运行, 这样才能让SparkSQL一直可以访问 Hive开启MetaStore-
Step 1: 修改hive-site.xml-
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/user/hive/warehouse</value>
</property> <property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://node01:3306/hive?createDatabaseIfNotExist=true</value>
</property> <property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property> <property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>username</value>
</property> <property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>password</value>
</property> <property>
<name>hive.metastore.local</name>
<value>false</value>
</property> <property>
<name>hive.metastore.uris</name>
<value>thrift://node01:9083</value> //当前服务器
</property> Step 2: 启动Hive MetaStore-
nohup /export/servers/hive/bin/hive --service metastore 2>&1 >> /var/log.log &
SparkSQL整合Hive的MetaStore-
即使不去整合
MetaStore,Spark也有一个内置的MateStore, 使用Derby嵌入式数据库保存数据, 但是这种方式不适合生产环境, 因为这种模式同一时间只能有一个SparkSession使用, 所以生产环境更推荐使用Hive的MetaStoreSparkSQL整合Hive的MetaStore主要思路就是要通过配置能够访问它, 并且能够使用HDFS保存WareHouse, 这些配置信息一般存在于Hadoop和HDFS的配置文件中, 所以可以直接拷贝Hadoop和Hive的配置文件到Spark的配置目录cd /export/servers/hadoop/etc/hadoop
cp hive-site.xml core-site.xml hdfs-site.xml /export/servers/spark/conf/ scp -r /export/servers/spark/conf node02:/export/servers/spark/conf
scp -r /export/servers/spark/conf node03:/export/servers/spark/confSpark需要hive-site.xml的原因是, 要读取Hive的配置信息, 主要是元数据仓库的位置等信息Spark需要core-site.xml的原因是, 要读取安全有关的配置Spark需要hdfs-site.xml的原因是, 有可能需要在HDFS中放置表文件, 所以需要HDFS的配置
|
如果不希望通过拷贝文件的方式整合 Hive, 也可以在 SparkSession 启动的时候, 通过指定 Hive 的 MetaStore 的位置来访问, 但是更推荐整合的方式 |
7.5.2. 访问 Hive 表
在
Hive中创建表使用
SparkSQL访问Hive中已经存在的表使用
SparkSQL创建Hive表使用
SparkSQL修改Hive表中的数据
- 在
Hive中创建表 -
第一步, 需要先将文件上传到集群中, 使用如下命令上传到
HDFS中hdfs dfs -mkdir -p /dataset
hdfs dfs -put studenttabl10k /dataset/第二步, 使用
Hive或者Beeline执行如下SQLCREATE DATABASE IF NOT EXISTS spark_integrition; USE spark_integrition; CREATE EXTERNAL TABLE student
(
name STRING,
age INT,
gpa string
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/dataset/hive'; LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student; - 通过
SparkSQL查询Hive的表(在spark集群中(即,只有使用sparkshell或sparksubmit时)才能整合hive) -
查询
Hive中的表可以直接通过spark.sql(…)来进行, 可以直接在其中访问Hive的MetaStore, 前提是一定要将Hive的配置文件拷贝到Spark的conf目录scala> spark.sql("use spark_integrition") #sql()方法会立刻执行,而不是transformation方法一样惰性求值
scala> val resultDF = spark.sql("select * from student limit 10")
scala> resultDF.show() - 通过
SparkSQL创建Hive表 -
通过
SparkSQL可以直接创建Hive表, 并且使用LOAD DATA加载数据val createTableStr =
"""
|CREATE EXTERNAL TABLE student
|(
| name STRING,
| age INT,
| gpa string
|)
|ROW FORMAT DELIMITED
| FIELDS TERMINATED BY '\t'
| LINES TERMINATED BY '\n'
|STORED AS TEXTFILE
|LOCATION '/dataset/hive'
""".stripMargin spark.sql("CREATE DATABASE IF NOT EXISTS spark_integrition1")
spark.sql("USE spark_integrition1")
spark.sql(createTableStr)
spark.sql("LOAD DATA INPATH '/dataset/studenttab10k' OVERWRITE INTO TABLE student")
spark.sql("select * from student limit").show()目前
SparkSQL支持的文件格式有sequencefile,rcfile,orc,parquet,textfile,avro, 并且也可以指定serde的名称 - 使用
SparkSQL处理数据并保存进 Hive 表 -
前面都在使用
SparkShell的方式来访问Hive, 编写SQL, 通过Spark独立应用的形式也可以做到同样的事, 但是需要一些前置的步骤, 如下- Step 1: 导入
Maven依赖 -
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_2.11</artifactId>
<version>${spark.version}</version>
</dependency> - Step 2: 配置
SparkSession -
如果希望使用
SparkSQL访问Hive的话, 需要做两件事开启
SparkSession的Hive支持经过这一步配置,
SparkSQL才会把SQL语句当作HiveSQL来进行解析设置
WareHouse的位置虽然
hive-stie.xml中已经配置了WareHouse的位置, 但是在Spark 2.0.0后已经废弃了hive-site.xml中设置的hive.metastore.warehouse.dir, 需要在SparkSession中设置WareHouse的位置设置
MetaStore的位置
val spark = SparkSession
.builder()
.appName("hive example")
.config("spark.sql.warehouse.dir", "hdfs://node01:8020/dataset/hive")
.config("hive.metastore.uris", "thrift://node01:9083")
.enableHiveSupport()
.getOrCreate()设置 WareHouse的位置设置 MetaStore的位置开启 Hive支持
配置好了以后, 就可以通过
DataFrame处理数据, 后将数据结果推入Hive表中了, 在将结果保存到Hive表的时候, 可以指定保存模式val schema = StructType(
List(
StructField("name", StringType),
StructField("age", IntegerType),
StructField("gpa", FloatType)
)
) val studentDF = spark.read
.option("delimiter", "\t")
.schema(schema)
.csv("dataset/studenttab10k") val resultDF = studentDF.where("age < 50") resultDF.write.mode(SaveMode.Overwrite).saveAsTable("spark_integrition1.student")通过 mode指定保存模式, 通过saveAsTable保存数据到Hive - Step 1: 导入
7.6. JDBC
通过
SQL操作MySQL的表将数据写入
MySQL的表中
- 准备
MySQL环境 -
在使用
SparkSQL访问MySQL之前, 要对MySQL进行一些操作, 例如说创建用户, 表和库等Step 1: 连接
MySQL数据库在
MySQL所在的主机上执行如下命令mysql -u root -pStep 2: 创建
Spark使用的用户登进
MySQL后, 需要先创建用户CREATE USER 'spark'@'%' IDENTIFIED BY 'Spark123!';
GRANT ALL ON spark_test.* TO 'spark'@'%';Step 3: 创建库和表
CREATE DATABASE spark_test; USE spark_test; CREATE TABLE IF NOT EXISTS `student`(
`id` INT AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`age` INT NOT NULL,
`gpa` FLOAT,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
- 使用
SparkSQL向MySQL中写入数据 -
其实在使用
SparkSQL访问MySQL是通过JDBC, 那么其实所有支持JDBC的数据库理论上都可以通过这种方式进行访问在使用
JDBC访问关系型数据的时候, 其实也是使用DataFrameReader, 对DataFrameReader提供一些配置, 就可以使用Spark访问JDBC, 有如下几个配置可用属性 含义 url要连接的
JDBC URLdbtable要访问的表, 可以使用任何
SQL语句中from子句支持的语法fetchsize数据抓取的大小(单位行), 适用于读的情况
batchsize数据传输的大小(单位行), 适用于写的情况
isolationLevel事务隔离级别, 是一个枚举, 取值
NONE,READ_COMMITTED,READ_UNCOMMITTED,REPEATABLE_READ,SERIALIZABLE, 默认为READ_UNCOMMITTED读取数据集, 处理过后存往
MySQL中的代码如下val spark = SparkSession
.builder()
.appName("hive example")
.master("local[6]")
.getOrCreate() val schema = StructType(
List(
StructField("name", StringType),
StructField("age", IntegerType),
StructField("gpa", FloatType)
)
) val studentDF = spark.read
.option("delimiter", "\t")
.schema(schema)
.csv("dataset/studenttab10k") studentDF.write.format("jdbc").mode(SaveMode.Overwrite)
.option("url", "jdbc:mysql://node01:3306/spark_test")
.option("dbtable", "student")
.option("user", "spark")
.option("password", "Spark123!")
.save() - 运行程序
-
如果是在本地运行, 需要导入
Maven依赖<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>如果使用
Spark submit或者Spark shell来运行任务, 需要通过--jars参数提交MySQL的Jar包, 或者指定--packages从Maven库中读取bin/spark-shell --packages mysql:mysql-connector-java:5.1.47 --repositories http://maven.aliyun.com/nexus/content/groups/public/ - 从
MySQL中读取数据 -
读取
MySQL的方式也非常的简单, 只是使用SparkSQL的DataFrameReader加上参数配置即可访问spark.read.format("jdbc")
.option("url", "jdbc:mysql://node01:3306/spark_test")
.option("dbtable", "student")
.option("user", "spark")
.option("password", "Spark123!")
.load()
.show()默认情况下读取
MySQL表时, 从MySQL表中读取的数据放入了一个分区, 拉取后可以使用DataFrame重分区来保证并行计算和内存占用不会太高, 但是如果感觉MySQL中数据过多的时候, 读取时可能就会产生OOM, 所以在数据量比较大的场景, 就需要在读取的时候就将其分发到不同的RDD分区属性 含义 partitionColumn指定按照哪一列进行分区, 只能设置类型为数字的列, 一般指定为
IDlowerBound,upperBound确定步长的参数,
lowerBound - upperBound之间的数据均分给每一个分区, 小于lowerBound的数据分给第一个分区, 大于upperBound的数据分给最后一个分区numPartitions分区数量
spark.read.format("jdbc")
.option("url", "jdbc:mysql://node01:3306/spark_test")
.option("dbtable", "student")
.option("user", "spark")
.option("password", "Spark123!")
.option("partitionColumn", "age")
.option("lowerBound", 1)
.option("upperBound", 60)
.option("numPartitions", 10)
.load()
.show()有时候可能要使用非数字列来作为分区依据,
Spark也提供了针对任意类型的列作为分区依据的方法val predicates = Array(
"age < 20",
"age >= 20, age < 30",
"age >= 30"
) val connectionProperties = new Properties()
connectionProperties.setProperty("user", "spark")
connectionProperties.setProperty("password", "Spark123!") spark.read
.jdbc(
url = "jdbc:mysql://node01:3306/spark_test",
table = "student",
predicates = predicates,
connectionProperties = connectionProperties
)
.show()SparkSQL中并没有直接提供按照SQL进行筛选读取数据的API和参数, 但是可以通过dbtable来曲线救国,dbtable指定目标表的名称, 但是因为dbtable中可以编写SQL, 所以使用子查询即可做到spark.read.format("jdbc")
.option("url", "jdbc:mysql://node01:3306/spark_test")
.option("dbtable", "(select name, age from student where age > 10 and age < 20) as stu")
.option("user", "spark")
.option("password", "Spark123!")
.option("partitionColumn", "age")
.option("lowerBound", 1)
.option("upperBound", 60)
.option("numPartitions", 10)
.load()
.show()
Update(Stage4):sparksql:第1节 SparkSQL_使用场景_优化器_Dataset & 第2节 SparkSQL读写_hive_mysql_案例的更多相关文章
- Update(Stage4):sparksql:第3节 Dataset (DataFrame) 的基础操作 & 第4节 SparkSQL_聚合操作_连接操作
8. Dataset (DataFrame) 的基础操作 8.1. 有类型操作 8.2. 无类型转换 8.5. Column 对象 9. 缺失值处理 10. 聚合 11. 连接 8. Dataset ...
- Update(Stage4):sparksql:第5节 SparkSQL_出租车利用率分析案例
目录: 1. 业务2. 流程分析3. 数据读取5. 数据清洗6. 行政区信息 6.1. 需求介绍 6.2. 工具介绍 6.3. 具体实现7. 会话统计 导读 本项目是 SparkSQL 阶段的练习项目 ...
- Update(Stage4):Structured Streaming_介绍_案例
1. 回顾和展望 1.1. Spark 编程模型的进化过程 1.2. Spark 的 序列化 的进化过程 1.3. Spark Streaming 和 Structured Streaming 2. ...
- IIS7错误:不能在此路径中使用此配置节。如果在父级别上锁定了该节,便会出现这种情况。锁定是默认设置的(overrideModeDefault="Deny")......
不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的(overrideModeDefault="Deny")...... 解决方案: 因为 II ...
- 不能在此路径中使用此配置节。如果在父级别上锁定了该节,便会出现这种情况。锁定是默认设置的(overrideModeDefault="Deny"),或者 是通过包含 overrideMode="Deny" 或旧有的 allowOverride="false" 的位置标记明确设置的。
问题: 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的(overrideModeDefault="Deny"),或者是通过包含 overr ...
- 第十一节,全连接网络中的优化技巧-过拟合、正则化,dropout、退化学习率等
随着科研人员在使用神经网络训练时不断的尝试,为我们留下了很多有用的技巧,合理的运用这些技巧可以使自己的模型得到更好的拟合效果. 一 利用异或数据集演示过拟合 全连接网络虽然在拟合问题上比较强大,但太强 ...
- IIS8 不能在此路径中使用此配置节。如果在父级别上锁定了该节
问题: 不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的(overrideModeDefault="Deny"),或者是通过包含 overr ...
- Android零基础入门第13节:Android Studio配置优化,打造开发利器
原文:Android零基础入门第13节:Android Studio配置优化,打造开发利器 是不是很多同学已经有烦恼出现了?电脑配置已经很高了,但是每次运行Android程序的时候就很卡,而且每次安装 ...
- 第2课第3节_Java面向对象编程_继承性_P【学习笔记】
摘要:韦东山android视频学习笔记 面向对象程序的三大特性之继承性:继承性的主要作用就是复用代码.继承性也有一定的限制,如图一 图一 1.我们在第2课第2节_Java面向对象编程_封装性_P 中 ...
随机推荐
- python 递归调用 返回值问题
当使用递归时并有返回值时,调用自身函数时需要加上return语句如下: def daxiao(biao1,biao2): #判断两个列表的大小,根据里面的元素大小 #如果biao1大于 ...
- 【转载】Spring MVC入门
转自:http://www.importnew.com/15141.html MVC框架是什么 模型-视图-控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计模式.它主要通过分离模型.视图 ...
- my97日期控件弹出位置显示异常
使用my97日期选择控件的时候,如果整个页面是有滚动条的,根据触发显示日期的控件的父控件的position不同会显示不同的情况 1.position不为fixed则滑动滚动条,显示的日期层不会出现异常 ...
- 第二十五篇 玩转数据结构——链表(Linked List)
1.. 链表的重要性 我们之前实现的动态数组.栈.队列,底层都是依托静态数组,靠resize来解决固定容量的问题,而"链表"则是一种真正的动态数据结构,不需要处理固定容 ...
- Bugku-CTF之这是一个神奇的登陆框
Day32 这是一个神奇的登陆框 http://123.206.87.240:9001/sql/ flag格式flag{}
- 510,position的值,relative和absolute定位原点是
(absolute:生成绝对定位的元素) position属性用来规定元素的定位类型和方式 ①position:static 默认值,没有定位,元素出现在正常的流中: ②position:fixed ...
- 【使用python urllib时出现[SSL: CERTIFICATE_VERIFY_FAILED]报错的解决方案】
"首先,这个报错是告诉你,你的证书有问题. 其次,出现这个问题的原因,在于Python本身. 问题原因 Python升级到2.7.9以后,引入了一个新特性. 当使用urllib打开https ...
- jvm字节码助记符
反编译指令 javap -c xxxx.class JVM参数设置 -xx:+<option> 开启option -xx: -<option> ...
- Hadoop 启动/停止集群和节点的命令
集群启动/停止Hadoop集群:start-all.sh stop-all.sh 1) 启动NameNode, DataNode 2) 启动JournalNode, JournalNode在hd ...
- c# 异常:值不能为 null。 参数名: source
异常详细信息: System.ArgumentNullException: 值不能为 null.参数名: source 其实问题那就出在 Select() 方法,在 Select 上按 F12 查看定 ...


