Java 序列化和Scala的闭包的区别和注意点
背景
最近在做一些scala闭包和java序列化的问题回顾,具体的是Spark DPP(动态分区裁剪)导致的DataSourceScanExec NullPointerException问题分析以及解决
在此记录一下,主要是文章说到的 DataSourceScanExec 中 maxMetadataValueLength字段的序列化和反序列化问题
trait DataSourceScanExec extends LeafExecNode {def relation: BaseRelationdef tableIdentifier: Option[TableIdentifier]protected val nodeNamePrefix: String = ""override val nodeName: String = {s"Scan $relation ${tableIdentifier.map(_.unquotedString).getOrElse("")}"}// Metadata that describes more details of this scan.protected def metadata: Map[String, String]protected val maxMetadataValueLength = sqlContext.sessionState.conf.maxMetadataStringLength...
在这里说到了DataSourceScanExec
的实现类FileSourceScanExec
的doCanonicalize
方法会new FileSourceScanExec对象(这里会在Executor调用),所以导致 maxMetadataValueLength
这行代码报空指针问题,因为 sqlContext 标记为了@transient,这样在新建该对象的时候就会初始化 maxMetadataValueLength ,所以就NPE了,
而之所以不会再Executor反序列化RDD的时候报错,是因为 maxMetadataValueLength 在序列化的时候,获取的值是 具体的整数值,会直接把该整数值给序列化,这样在反序列化的时候,直接获取的就是对应的整数值。
Java序列化
java序列化的时候,只会序列化实例变量所引用的对象,如
SparkConf confRef = conf
如果是以下这种就不会序列化对应的SparkConf对象:
int maxMetadataValueLength = conf.maxMetadataStringLength
这是因为该maxMetadataValueLength
已经获取到了对应的整数类型值了,不需要conf了。
Scala闭包
什么是Scala闭包
Scala 闭包是一种函数,它能够捕获并包含其定义环境中的自由变量,使其在函数外部的作用域结束后依然可用。 闭包由两部分组成:一个函数和该函数引用的外部变量。 这种机制允许函数访问和操作其定义时所在的上下文变量,即使在调用该函数时外部变量已经超出作用域
如果此时你这么定义:
val conf = new SparkConf().set("key", "value")val processFunc = (x: Int) => {val maxMetadataValueLength = conf.maxMetadataStringLength // 闭包内部定义x + maxMetadataValueLength
}
这个时候闭包就会引用conf 对象,也就是说该conf对象会在闭包中存在一个成员变量并引用该对象。在spark中 ClosureCleaner 会 clone 一份该对象。
那如何规避,在闭包中不引用该对象呢:
可以通过 预计算值方法,如下:
val conf = new SparkConf().set("key", "value")
val maxMetadataValueLength = conf.maxMetadataStringLength // 先计算val processFunc = (x: Int) => {x + maxMetadataValueLength // 闭包只引用值,不引用 conf
}