Spark的官方文档再三强调那些将要作用到RDD上的操作,不管它们是一个函数还是一段代码片段,它们都是“闭包”,Spark会把这个闭包分发到各个worker节点上去执行,这里涉及到了一个容易被忽视的问题:闭包的“序列化”。

显然,闭包是有状态的,这主要是指它牵涉到的那些自由变量以及自由变量依赖到的其他变量,所以,在将一个简单的函数或者一段代码片段(就是闭包)传递给类似RDD.map这样的操作前,Spark需要检索闭包内所有的涉及到的变量(包括传递依赖的变量),正确地把这些变量序列化之后才能传递到worker节点并反序列化去执行。如果在涉及到的所有的变量中有任何不支持序列化或没有指明如何序列化自己时,你就会遇到这样的错误:

org.apache.spark.SparkException: Task not serializable

在下面的例子中,我们从kafka中持续地接收json消息,并在spark-streaming中将字符串解析成对应的实体:

object App {
private val config = ConfigFactory.load("my-streaming.conf")
case class Person (firstName: String,lastName: String)
def main(args: Array[String]) {
val zkQuorum = config.getString("kafka.zkQuorum")
val myTopic = config.getString("kafka.myTopic")
val myGroup = config.getString("kafka.myGroup")
val conf = new SparkConf().setAppName("my-streaming")
val ssc = new StreamingContext(conf, Seconds())
val lines = KafkaUtils.createStream(ssc, zkQuorum, myGroup, Map(myTopic -> ))
//this val is a part of closure, and it's not serializable!
implicit val formats = DefaultFormats
def parser(json: String) = parse(json).extract[Person].firstName
lines.map(_._2).map(parser).print
....
ssc.start()
ssc.awaitTerminationOrTimeout()
ssc.stop()
} }

这段代码在执行时就会报如下错误:

org.apache.spark.SparkException: Task not serializable
Caused by: java.io.NotSerializableException: org.json4s.DefaultFormats$

问题的症结就在于:闭包没有办法序列化。在这个例子里,闭包的范围是:函数parser以及它所依赖的一个隐式参数: formats , 而问题就出在这个隐式参数上, 它的类型是DefaultFormats,这个类没有提供序列化和反序列自身的说明,所以Spark无法序列化formats,进而无法将task推送到远端执行。

隐式参数formats是为extract准备的,它的参数列表如下:

org.json4s.ExtractableJsonAstNode#extract[A](implicit formats: Formats, mf: scala.reflect.Manifest[A]): A = ...

找到问题的根源之后就好解决了。实际上我们根本不需要序列化formats, 对我们来说,它是无状态的。所以,我们只需要把它声明为一个全局静态的变量就可以绕过序列化。所以改动的方法就是简单地把implicit val formats = DefaultFormats的声明从方法内部迁移到App Object的字段位置上即可。

object App {
private val config = ConfigFactory.load("my-streaming.conf")
case class Person (firstName: String,lastName: String)
//As Object field, global, static, no need to serialize
implicit val formats = DefaultFormats def main(args: Array[String]) {
val zkQuorum = config.getString("kafka.zkQuorum")
val myTopic = config.getString("kafka.myTopic")
val myGroup = config.getString("kafka.myGroup")
val conf = new SparkConf().setAppName("my-streaming")
val ssc = new StreamingContext(conf, Seconds())
val lines = KafkaUtils.createStream(ssc, zkQuorum, myGroup, Map(myTopic -> ))
def parser(json: String) = parse(json).extract[Person].firstName
lines..map(_._2).map(parser).print
....
ssc.start()
ssc.awaitTerminationOrTimeout()
ssc.stop()
} }

这里再提供另外一个很好的例子:

这个例子很好演示了解决类似问题的方案:“把类成员变量拷贝一份到闭包中” ,不然整个对象都需要被序列化!

最后我们来总结一下应该如何正确的处理Spark Task闭包的序列化问题。首先你需要对Task涉及的闭包的边界要有一个清晰的认识,要尽量地控制闭包的范围和牵涉到的自由变量,一个非常值得警惕的地方是:尽量不要在闭包中直接引用一个类的成员变量和函数,这样会导致整个类实例被序列化。这样的例子在Spark文档中也有提及,如下:

class MyClass {
def func1(s: String): String = { ... }
def doStuff(rdd: RDD[String]): RDD[String] = { rdd.map(func1) }
}

然后,一个好的组织代码的方式是:除了那些很短小的函数,尽量把复杂的操作封装到全局单一的函数体:全局静态方法或者函数对象

如果确实需要某个类的实例参与到计算过程中,则要作好相关的序列化工作。

Spark闭包与序列化的更多相关文章

  1. Spark闭包 | driver & executor程序代码执行

    Spark中的闭包 闭包的作用可以理解为:函数可以访问函数外部定义的变量,但是函数内部对该变量进行的修改,在函数外是不可见的,即对函数外源变量不会产生影响. 其实,在学习Spark时,一个比较难理解的 ...

  2. Spark:将RDD[List[String,List[Person]]]中的List[Person]通过spark api保存为hdfs文件时一直出现not serializable task,没办法找到"spark自定义Kryo序列化输入输出API"

    声明:本文转自<在Spark中自定义Kryo序列化输入输出API>   在Spark中内置支持两种系列化格式:(1).Java serialization:(2).Kryo seriali ...

  3. spark优化:spark.serializer修改序列化方式

    进行节点的数据传递,或者保存数据时都会进行序列化.spark默认的是org.apache.spark.serializer.JavaSerializer.而我们要修改成org.apache.spark ...

  4. Spark设置Kryo序列化缓冲区大小

    背景 今天在开发SparkRDD的过程中出现Buffer Overflow错误,查看具体Yarn日志后发现是因为Kryo序列化缓冲区溢出了,日志建议调大spark.kryoserializer.buf ...

  5. Spark性能优化(1)——序列化、内存、并行度、数据存储格式、Shuffle

    序列化 背景: 在以下过程中,需要对数据进行序列化: shuffling data时需要通过网络传输数据 RDD序列化到磁盘时 性能优化点: Spark默认的序列化类型是Java序列化.Java序列化 ...

  6. Spark 序列化问题

    在Spark应用开发中,很容易出现如下报错: org.apache.spark.SparkException: Task not serializable at org.apache.spark.ut ...

  7. spark新能优化之序列化

    概叙: 在任何分布式系统中,序列化都是扮演着一个重要的角色的.如果使用的序列化技术,在执行序列化操作的时候很慢,或者是序列化后的数据还是很大,那么会让分布式应用程序的性能下降很多.所以,进行Spark ...

  8. 【Spark调优】Kryo序列化

    [Java序列化与反序列化] Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程.序列化使用场景:1.数据的持久化,通过序列化可以把数据永久 ...

  9. Spark 性能相关参数配置详解-压缩与序列化篇

    随着Spark的逐渐成熟完善, 越来越多的可配置参数被添加到Spark中来, 本文试图通过阐述这其中部分参数的工作原理和配置思路, 和大家一起探讨一下如何根据实际场合对Spark进行配置优化. 由于篇 ...

随机推荐

  1. pycharm动态设置字体放大缩小

    放大设置 File —> settings—> Keymap —>在搜寻框中输入:increase —> Increase Font Size(双击) —> 在弹出的对话 ...

  2. linux cgroups简介(上)

    Linux CGroups简介 1.CGroups是什么 与Linux namespace对比来看,Linux namespace用来限制进程的运行范围或者运行环境的可见性,比如:uts限制进程读取到 ...

  3. zabbix--监控MySQL主从状态

    zabbix监控MySQL主从状态 搭建MySQL主从后,很多时候不知道从的状态是否ok,有时候出现异常不能及时知道,这里通过shell脚本结合zabbix实现监控并告警 一般情况下,在MySQL的从 ...

  4. Huffman Tree (哈夫曼树学习)

    WPL 和哈夫曼树 哈夫曼树,又称最优二叉树,是一棵带权值路径长度(WPL,Weighted Path Length of Tree)最短的树,权值较大的节点离根更近. 首先介绍一下什么是 WPL,其 ...

  5. The 2016 ACM-ICPC Asia China-Final D. Ice Cream Tower 二分 + 贪心

    题目大意: 对于给出的n个冰激凌球的大小,满足下面的球的大小是上一个的至少2倍,对于给出的k(由k的冰激凌球才能算作一个冰激凌塔),问n个冰激凌球可以最多堆出多少个高度为k的冰激凌塔 题目分析: 对于 ...

  6. 第3章 常用linux命令 3.5 文件压缩命令

    实验六 文件及目录的压缩解压缩相关命令的使用 [实验目的] 1.掌握linux压缩文件实质 2.掌握linux中压缩及解压缩指令的用法 [实验环境] 1. 标准配置PC一台 2. linux操作系统: ...

  7. Goland在go mod vendor模式下无法识别某些库

    症状:go build可以正常编译,但代码编辑器里面提示找不到相关lib,后来发现是因为go.mod中没有用require这个库,补上库地址和版本.因为项目的mod vendor模式,版本一般不需要写 ...

  8. new String("123") 创建了几个对象?

    String 对象可谓再熟悉不过了,与此相关的面试题经常会引出内存性能优化的问题,本篇主要以 new String("123") 创建了几个对象为例记录. 一.你能回答正确吗 St ...

  9. 上传OSS报错

    修改OSS

  10. VirtualBox support USB3.0 funciton

    首先确认Oracle VM VirtualBox 的版本 然后通过如下链接下载对应的扩展增强包: http://download.virtualbox.org/virtualbox 再则,通过管理-& ...