什么时候需要序列化?
在实际开发中我们往往需要自己定义一些对于RDD的操作,那么需要注意的是,初始化工作是在Driver端进行的,而程序实际运行是在Executor端进行的
,2者有可能不在同一节点,那么就涉及跨进程通信了,所以要进行序列化
进一步来说, 在Spark中,算子相关的操作在Excutor上执行,算子之外的代码在Driver端执行
,在执行有些算子
的时候,需要使用到Driver里面定义的数据
,这就涉及到了跨节点或者跨进程之间的通信,所以要求传递给Excutor的数据所属的类型必须实现Serializable接口
如何判读是否实现了Serializable?
在作业job提交之前
,其中有一行代码, val cleanF = sc.clean(f), 用于进行闭包检测,
之所以叫闭包检测,是因为当前函数的内部访问了外部函数的变量,属于闭包的形式
错误演示
算子相关的操作在Excutor上执行,算子之外的代码在Driver端执行
package com.xcu.bigdata.spark.core.pg02_serializable
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Spark01_Serializable {
def main(args: Array[String]): Unit = {
//创建配置文件
val conf: SparkConf = new SparkConf().setAppName("Spark01_Serializable").setMaster("local[*]")
//创建SparkContext,该对象是提交的入口
val sc = new SparkContext(conf)
//创建2个user对象
val user1 = new User
user1.name = "zhangsan"
val user2 = new User
user2.name = "lisi"
//将两个user对象封装成一个List
val rdd: RDD[User] = sc.parallelize(List(user1, user2))
//打印user.name属于foreach算子相关的操作,在Excutor上执行
//像创建new SparkContext、new User等属于算子以外的代码,在Driver端执行
//如果不序列化,会出错
rdd.foreach((user: User) => println(user.name))
//释放资源
sc.stop()
}
}
class User {
// 给name属性赋默认值
var name: String = _
}
异常:
java.io.NotSerializableException
解决办法:
class User extends Serializable {
// 给name属性赋默认值
var name: String = _
}
序列化方法和属性
package com.xcu.bigdata.spark.core.pg02_serializable
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Desc : 序列化方法和属性
*/
object Spark02_Serializable {
def main(args: Array[String]): Unit = {
//创建配置文件
val conf: SparkConf = new SparkConf().setAppName("Spark02_Serializable").setMaster("local[*]")
//创建SparkContext,该对象是提交的入口
val sc = new SparkContext(conf)
//创建RDD
val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello spark", "hive", "scala"))
//创建一个Search对象
val search = new Search("hello")
//Driver:算子以外的代码都是在Driver端执行
//Executor:算子里面的代码都是在Executor端执行
//函数传递
search.getMatch1(rdd).collect()
//属性传递
search.getMatch2(rdd).collect().foreach(println)
//释放资源
sc.stop()
}
}
class Search(query: String) {
def isMatch(s: String): Boolean = {
s.contains(query)
}
//函数序列化案例
def getMatch1(rdd: RDD[String]): RDD[String] = {
//rdd.filter(this.isMatch)
rdd.filter(isMatch)
}
//属性序列化案例
def getMatch2(rdd: RDD[String]): RDD[String] = {
//rdd.filter(x => x.contains(this.query))
rdd.filter((x: String) => x.contains(query))
//val q =query
//rdd.filter(x => x.contains(q))
}
}
问题一说明
//过滤出包含字符串的RDD
def getMatch1 (rdd: RDD[String]): RDD[String] = {
rdd.filter(isMatch)
}
(1)在这个方法中所调用的方法isMatch()是定义在Search这个类中的,实际上调用的是this. isMatch(),this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。
(2)解决方案
类继承scala.Serializable即可。
class Search() extends Serializable{...}
问题二说明
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
rdd.filter(x => x.contains(query))
}
(1)在这个方法中所调用的方法query是定义在Search这个类中的字段,实际上调用的是this. query,this表示Search这个类的对象,程序在运行过程中需要将Search对象序列化以后传递到Executor端。
(2)解决方案一
(a)类继承scala.Serializable即可。
class Search() extends Serializable{...}
(b)将类变量query赋值给局部变量
//过滤出包含字符串的RDD
def getMatche2(rdd: RDD[String]): RDD[String] = {
val q = this.query//将类变量赋值给局部变量
rdd.filter(x => x.contains(q))
}
解决方案二
把Search类变成样例类,样例类默认是序列化的。
case class Search(query:String) {...}
Kryo序列化框架
Java的序列化能够序列化任何的类。但是比较重,
序列化后对象的提交也比较大.
Spark出于性能的考虑,Spark2.0开始支持另外一种Kryo序列化机制。
Kryo速度是Serializable的10倍。当RDD在Shuffle数据的时候,简单数据类型、数组和字符串类型已经在Spark内部使用kryo来序列化
注意:即使使用kryo序列化,也要继承Serializable接口
package com.xcu.bigdata.spark.core.pg02_serializable
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* @Desc : kryo的简单使用
*/
object Spark03_TestKryo {
def main(args: Array[String]): Unit = {
val conf: SparkConf = new SparkConf()
.setAppName("Spark03_TestKryo")
.setMaster("local[*]")
// 替换默认的序列化机制
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 注册需要使用 kryo 序列化的自定义类
.registerKryoClasses(Array(classOf[Searcher]))
//创建SparkContext,该对象是提交的入口
val sc = new SparkContext(conf)
//创建RDD
val rdd: RDD[String] = sc.makeRDD(Array("hello world", "hello scala", "spark", "java"), numSlices = 2)
//创建一个Search对象
val searcher = new Searcher("hello")
//过滤特定字符串
val result: RDD[String] = searcher.getMatchedRDD1(rdd)
//打印输出
result.collect.foreach(println)
}
}
//样例类默认是序列化的
case class Searcher(val query: String) {
def isMatch(s: String): Boolean = {
s.contains(query)
}
def getMatchedRDD1(rdd: RDD[String]): RDD[String] = {
rdd.filter(isMatch)
}
}