目录
Step 2: 在 Spark 中的序列化和反序列化的应用场景
Step 4: DataFrame 和 Dataset 中的序列化
1. Spark 编程模型的进化过程
目标和过程
本章目标
Structured Streaming
是Spark Streaming
的进化版, 如果了解了Spark
的各方面的进化过程, 有助于理解Structured Streaming
的使命和作用
本章过程
Spark
的API
进化过程
Spark
的序列化进化过程
Spark Streaming
和Structured Streaming
1.1 Spark 编程模型的进化过程
目标和过程
目标
Spark
的进化过程中, 一个非常重要的组成部分就是编程模型的进化, 通过编程模型可以看得出来内在的问题和解决方案过程
编程模型
RDD
的优点和缺陷编程模型
DataFrame
的优点和缺陷编程模型
Dataset
的优点和缺陷
编程模型 | 解释 |
---|---|
|
|
|
|
|
|
总结
RDD
的优点
面向对象的操作方式
可以处理任何类型的数据
RDD
的缺点
运行速度比较慢, 执行过程没有优化
API
比较僵硬, 对结构化数据的访问和操作没有优化
DataFrame
的优点
针对结构化数据高度优化, 可以通过列名访问和转换数据
增加
Catalyst
优化器, 执行过程是优化的, 避免了因为开发者的原因影响效率
DataFrame
的缺点
只能操作结构化数据
只有无类型的
API
, 也就是只能针对列和SQL
操作数据,API
依然僵硬
Dataset
的优点
结合了
RDD
和DataFrame
的API
, 既可以操作结构化数据, 也可以操作非结构化数据既有有类型的
API
也有无类型的API
, 灵活选择
1.2 Spark 的 序列化 的进化过程
目标和过程
目标
Spark
中的序列化过程决定了数据如何存储, 是性能优化一个非常重要的着眼点,Spark
的进化并不只是针对编程模型提供的API
, 在大数据处理中, 也必须要考虑性能过程
序列化和反序列化是什么
Spark
中什么地方用到序列化和反序列化
RDD
的序列化和反序列化如何实现
Dataset
的序列化和反序列化如何实现
Step 1: 什么是序列化和序列化
在 Java
中, 序列化的代码大概如下
public class JavaSerializable implements Serializable {
NonSerializable ns = new NonSerializable();
}
public class NonSerializable {
}
public static void main(String[] args) throws IOException {
// 序列化
JavaSerializable serializable = new JavaSerializable();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("/tmp/obj.ser"));
// 这里会抛出一个 "java.io.NotSerializableException: cn.itcast.NonSerializable" 异常
objectOutputStream.writeObject(serializable);
objectOutputStream.flush();
objectOutputStream.close();
// 反序列化
FileInputStream fileInputStream = new FileInputStream("/tmp/obj.ser");
ObjectInputStream objectOutputStream = new ObjectInputStream(fileInputStream);
JavaSerializable serializable1 = objectOutputStream.readObject();
}
序列化是什么
-
序列化的作用就是可以将对象的内容变成二进制, 存入文件中保存
-
反序列化指的是将保存下来的二进制对象数据恢复成对象
序列化对对象的要求
-
对象必须实现
Serializable
接口 -
对象中的所有属性必须都要可以被序列化, 如果出现无法被序列化的属性, 则序列化失败
限制
-
对象被序列化后, 生成的二进制文件中, 包含了很多环境信息, 如对象头, 对象中的属性字段等, 所以内容相对较大
-
因为数据量大, 所以序列化和反序列化的过程比较慢
序列化的应用场景
-
持久化对象数据
-
网络中不能传输
Java
对象, 只能将其序列化后传输二进制数据
Step 2: 在 Spark
中的序列化和反序列化的应用场景
(1)Task
分发
Task
是一个对象, 想在网络中传输对象就必须要先序列化
(2)RDD
缓存
val rdd1 = rdd.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
rdd1.cache
rdd1.collect
-
RDD
中处理的是对象, 例如说字符串,Person
对象等 -
如果缓存
RDD
中的数据, 就需要缓存这些对象 -
对象是不能存在文件中的, 必须要将对象序列化后, 将二进制数据存入文件
(3)广播变量
广播变量会分发到不同的机器上, 这个过程中需要使用网络, 对象在网络中传输就必须先被序列化
(4)Shuffle
过程
Shuffle
过程是由 Reducer
从 Mapper
中拉取数据, 这里面涉及到两个需要序列化对象的原因
-
RDD
中的数据对象需要在Mapper
端落盘缓存, 等待拉取 -
Mapper
和Reducer
要传输数据对象
(5)Spark Streaming
的 Receiver
Spark Streaming
中获取数据的组件叫做 Receiver
, 获取到的数据也是对象形式, 在获取到以后需要落盘暂存, 就需要对数据对象进行序列化
(6)算子引用外部对象
class Unserializable(i: Int)
rdd.map(i => new Unserializable(i))
.collect
.foreach(println)
-
在
Map
算子的函数中, 传入了一个Unserializable
的对象 -
Map
算子的函数是会在整个集群中运行的, 那Unserializable
对象就需要跟随Map
算子的函数被传输到不同的节点上 -
如果
Unserializable
不能被序列化, 则会报错
Step 3: RDD
的序列化
RDD
的序列化
RDD 的序列化只能使用 Java 序列化器, 或者 Kryo 序列化器
为什么?
-
RDD 中存放的是数据对象, 要保留所有的数据就必须要对对象的元信息进行保存, 例如对象头之类的
-
保存一整个对象, 内存占用和效率会比较低一些
Kryo
是什么
-
Kryo
是Spark
引入的一个外部的序列化工具, 可以增快RDD
的运行速度 -
因为
Kryo
序列化后的对象更小, 序列化和反序列化的速度非常快 -
在
RDD
中使用Kryo
的过程如下
val conf = new SparkConf()
.setMaster("local[2]")
.setAppName("KyroTest")
conf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
conf.registerKryoClasses(Array(classOf[Person]))
val sc = new SparkContext(conf)
rdd.map(arr => Person(arr(0), arr(1), arr(2)))
Step 4: DataFrame
和 Dataset
中的序列化
历史的问题
RDD
中无法感知数据的组成, 无法感知数据结构, 只能以对象的形式处理数据
DataFrame
和 Dataset
的特点
DataFrame
和 Dataset
是为结构化数据优化的
在 DataFrame
和 Dataset
中, 数据和数据的 Schema
是分开存储的
spark.read
.csv("...")
.where($"name" =!= "")
.groupBy($"name")
.map(row: Row => row)
.show()
DataFrame
中没有数据对象这个概念, 所有的数据都以行的形式存在于 Row
对象中, Row
中记录了每行数据的结构, 包括列名, 类型等
Dataset
中上层可以提供有类型的 API
, 用以操作数据, 但是在内部, 无论是什么类型的数据对象 Dataset
都使用一个叫做 InternalRow
的类型的对象存储数据
val dataset: Dataset[Person] = spark.read.csv(...).as[Person]
(1) 优化点 1: 元信息独立
1.RDD
不保存数据的元信息, 所以只能使用 Java Serializer
或者 Kyro Serializer
保存 整个对象
2.DataFrame
和 Dataset
中保存了数据的元信息, 所以可以把元信息独立出来分开保存
3.一个 DataFrame
或者一个 Dataset
中, 元信息只需要保存一份, 序列化的时候, 元信息不需要参与
4.在反序列化 ( InternalRow → Object
) 时加入 Schema
信息即可
元信息不再参与序列化, 意味着数据存储量的减少, 和效率的增加
(2) 优化点 2: 使用堆外内存
-
DataFrame
和Dataset
不再序列化元信息, 所以内存使用大大减少. 同时新的序列化方式还将数据存入堆外内存中, 从而避免GC
的开销. -
堆外内存又叫做
Unsafe
, 之所以叫不安全的, 因为不能使用Java
的垃圾回收机制, 需要自己负责对象的创建和回收, 性能很好, 但是不建议普通开发者使用, 毕竟不安全
1.3 总结
-
当需要将对象缓存下来的时候, 或者在网络中传输的时候, 要把对象转成二进制, 在使用的时候再将二进制转为对象, 这个过程叫做序列化和反序列化
-
在
Spark
中有很多场景需要存储对象, 或者在网络中传输对象-
Task
分发的时候, 需要将任务序列化, 分发到不同的Executor
中执行 -
缓存
RDD
的时候, 需要保存RDD
中的数据 -
广播变量的时候, 需要将变量序列化, 在集群中广播
-
RDD
的Shuffle
过程中Map
和Reducer
之间需要交换数据 -
算子中如果引入了外部的变量, 这个外部的变量也需要被序列化
-
-
RDD
因为不保留数据的元信息, 所以必须要序列化整个对象, 常见的方式是Java
的序列化器, 和Kyro
序列化器 -
Dataset
和DataFrame
中保留数据的元信息, 所以可以不再使用Java
的序列化器和Kyro
序列化器, 使用Spark
特有的序列化协议, 生成UnsafeInternalRow
用以保存数据, 这样不仅能减少数据量, 也能减少序列化和反序列化的开销, 其速度大概能达到RDD
的序列化的20
倍左右
1.4 我的笔记
Spark中关于序列化的实现,是将数据和数据结构分开的,这样数据结构(元数据信息)只需一份,数据在存储时不用考虑管数据结构,解析时拿到数据结构进行解析即可,这样就提高了效率。