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. tensorflow--非线性回归

    算法步骤: 1. 给定训练样本,x_data和y_data 2. 定义两个占位符分别接收输入x和输出y 3. 中间层操作实际为:权值w与输入x矩阵相乘,加上偏差b后,得到中间层输出 4. 使用tanh ...

  2. phpstorm webstorm编辑器正则替换 类名,方法名替换

    首先勾选Match Case 和 Regex 正则规则:无须添加//左右分解符,直接写正则表达式,注意应该转义的部分,需要原封不动替换的部分加上括号 替换规则:正常书写正则,要继承下来的字符使用$1. ...

  3. Layout POJ - 3169

    题目链接:https://vjudge.net/problem/POJ-3169 题意:有一些奶牛,有些奶牛相互喜欢,他们之间的距离必须小于等于一个K. 有些奶牛相互讨厌,他们之间的距离必须大于等于一 ...

  4. 说说openjdk及G1回收器日志内容详细分析

    谈谈openjdk: 在正式往下学习JVM之前,这里谈谈openjdk这个网站,这个在学习java并发时也用过它来分析过锁的底层实现,如:https://www.cnblogs.com/webor20 ...

  5. fastjson =< 1.2.47 反序列化漏洞复现

    fastjson =< 1.2.47 反序列化漏洞复现 HW期间爆出来一个在hw期间使用的fastjson 漏洞,该漏洞无需开启autoType即可利用成功,建议使用fastjson的用户尽快升 ...

  6. flutter 从创建到渲染的大体流程

    从创建到渲染的大体流程是:根据Widget生成Element,然后创建相应的RenderObject并关联到Element.renderObject属性上,最后再通过RenderObject来完成布局 ...

  7. mysql导出PDM表结构并带有注释

    分享链接:https://www.cnblogs.com/dennyzhangdd/p/5550868.html

  8. [译] 揭秘 iOS 布局

    翻译自:Demystifying iOS Layout 在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题.通常这种问题发生的原因就是对于 view 何时真正更新的错误理解.想理 ...

  9. MongoDB 实现多key group by 并实现 having

    1.group by多个key db.testcol.aggregate(    {"$group": {_id:{card:"$card",account:& ...

  10. MongoDB shell 1 数据库方法

    方法名 描述 db.cloneDatabase() 从指定主机上克隆数据库 db.currentOp() 显示当前正在进行的操作 db.commandHelp() 返回数据库命令的帮助信息 db.cr ...