什么是序列化和序列化?
- 序列化是什么
1. 序列化的作用就是可以将对象的内容变成二进制, 存入文件中保存
2. 反序列化指的是将保存下来的二进制对象数据恢复成对象 - 序列化对对象的要求
1. 对象必须实现Serializable
接口
2. 对象中的所有属性必须都要可以被序列化, 如果出现无法被序列化的属性, 则序列化失败 - 限制
1. 对象被序列化后, 生成的二进制文件中, 包含了很多环境信息, 如对象头, 对象中的属性字段等, 所以内容相对较大
2. 因为数据量大, 所以序列化和反序列化的过程比较慢 - 序列化的应用场景
1. 持久化对象数据
2. 网络中不能传输Java
对象, 只能将其序列化后传输二进制数据
在 Spark
中的序列化和反序列化的应用场景
-
Task
分发
Task
是一个对象, 想在网络中传输对象就必须要先序列化
RDD
缓存
val rdd1 = rdd.flatMap(_.split(" "))
.map((_, 1))
.reduceByKey(_ + _)
rdd1.cache
rdd1.collect
-
RDD
中处理的是对象, 例如说字符串,Person
对象等 - 如果缓存
RDD
中的数据, 就需要缓存这些对象 - 对象是不能存在文件中的, 必须要将对象序列化后, 将二进制数据存入文件
- 广播变量
- 广播变量会分发到不同的机器上, 这个过程中需要使用网络, 对象在网络中传输就必须先被序列化
Shuffle
过程
Shuffle
过程是由Reducer
从Mapper
中拉取数据, 这里面涉及到两个需要序列化对象的原因
-
RDD
中的数据对象需要在Mapper
端落盘缓存, 等待拉取 -
Mapper
和Reducer
要传输数据对象
Spark Streaming
的Receiver
Spark Streaming
中获取数据的组件叫做Receiver
, 获取到的数据也是对象形式, 在获取到以后需要落盘暂存, 就需要对数据对象进行序列化- 算子引用外部对象
class userserializable(i: Int)
rdd.map(i => new Unserializable(i))
.collect
.foreach(println)
- 在
Map
算子的函数中, 传入了一个Unserializable
的对象 -
Map
算子的函数是会在整个集群中运行的, 那Unserializable
对象就需要跟随Map
算子的函数被传输到不同的节点上 - 如果
Unserializable
不能被序列化, 则会报错
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)))
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]
总结
- 当需要将对象缓存下来的时候, 或者在网络中传输的时候, 要把对象转成二进制, 在使用的时候再将二进制转为对象, 这个过程叫做序列化和反序列化
- 在
Spark
中有很多场景需要存储对象, 或者在网络中传输对象
-
Task
分发的时候, 需要将任务序列化, 分发到不同的Executor
中执行 - 缓存
RDD
的时候, 需要保存RDD
中的数据 - 广播变量的时候, 需要将变量序列化, 在集群中广播
-
RDD
的Shuffle
过程中Map
和Reducer
之间需要交换数据 - 算子中如果引入了外部的变量, 这个外部的变量也需要被序列化
-
RDD
因为不保留数据的元信息, 所以必须要序列化整个对象, 常见的方式是Java
的序列化器, 和Kyro
序列化器 -
Dataset
和DataFrame
中保留数据的元信息, 所以可以不再使用Java
的序列化器和Kyro
序列化器, 使用Spark
特有的序列化协议, 生成UnsafeInternalRow
用以保存数据, 这样不仅能减少数据量, 也能减少序列化和反序列化的开销, 其速度大概能达到RDD
的序列化的20
倍左右