Spark - 使用不可序列化的成员序列化对象

时间:2018-01-21 19:53:24

标签: java scala apache-spark serialization kryo

我将在Spark的上下文中提出这个问题,因为这就是我所面临的问题,但这可能是一个普通的Java问题。

在我们的火花工作中,我们有一个Resolver需要在我们所有的工人中使用(它在udf中使用)。问题是它不可序列化,我们无法改变它。解决方案是将其作为 可序列化的另一个类的成员。

所以我们最终得到了:

public class Analyzer implements Serializable {
    transient Resolver resolver;

    public Analyzer() {
        System.out.println("Initializing a Resolver...");
        resolver = new Resolver();
    }

    public int resolve(String key) {
         return resolver.find(key);
    }
}

然后我们使用Spark API broadcast这个类:

 val analyzer = sparkContext.broadcast(new Analyzer())

(可以找到有关Spark广播的更多信息here

然后我们继续在UDF中使用analyzer,作为我们的火花代码的一部分,例如:

val resolve = udf((key: String) => analyzer.value.resolve(key))
val result = myDataFrame.select("key", resolve("key")).count()

这一切都按预期工作,但让我们感到疑惑。

Resolver未实现Serializable,因此标记为transient - 意味着它不会与其所有者对象Analyzer一起序列化。

但正如您从上面的代码中可以清楚地看到的那样,resolve()方法使用resolver,因此它不能为空。事实上并非如此。代码有效。

因此,如果字段未通过序列化传递,resolver成员如何实例化?

我最初的想法是,可能在接收端调用Analyzer构造函数(即spark工作者),但是我希望看到行"Initializing a Resolver..."多次打印。但它只打印一次,这可能表明它只被调用一次,就在它传递给广播API之前。那么为什么不resolver null?

我是否遗漏了有关JVM序列化或Spark序列化的内容?

此代码如何工作?

Spark以cluster模式在YARN上运行。 spark.serializer设置为org.apache.spark.serializer.KryoSerializer

1 个答案:

答案 0 :(得分:3)

  

因此,如果该字段未通过序列化传递,那么该如何?   解析器成员实例化?

在调用new Resolver时,它通过构造函数调用(kryo.readObject)进行实例化:

kryo.readClassAndObject(input).asInstanceOf[T]
  

我最初的想法是,可能会调用Analyzer构造函数   在接收方(即火花工人),但我会期待   看几行打印“初始化旋转变压器......”。   但它只打印一次,这可能是一个迹象   事实上它只被叫一次

这不是广播变量的工作原理。会发生的情况是,当每个Executor需要范围内的广播变量时​​,它首先检查它的BlockManager内存中是否有对象,如果没有,则询问驱动程序或邻居执行程序(如果有)对于它们的缓存实例,它们是同一个Worker节点上的多个执行程序),它们将它序列化并发送给他,然后他接收实例并将其缓存在他自己的BlockManager中。

TorrentBroadcast(默认广播实施)的行为记录了这一点:

* The driver divides the serialized object into small chunks and
* stores those chunks in the BlockManager of the driver.
*
* On each executor, the executor first attempts to fetch the object from its BlockManager. If
* it does not exist, it then uses remote fetches to fetch the small chunks from the driver and/or
* other executors if available. Once it gets the chunks, it puts the chunks in its own
* BlockManager, ready for other executors to fetch from.
*
* This prevents the driver from being the bottleneck in sending out multiple copies of the
* broadcast data (one per executor).
  

如果我们删除它失败的瞬态,并且堆栈跟踪导致Kryo

这是因为即使Kryo无法序列化,Resolver类中也可能存在一个字段,无论Serializable属性如何。