[protocolbuffers/protobuf]Protobuf 和 Spark 的问题

2024-05-13 212 views
2

大家好,我想了解您对我们在一些 Spark 作业中尝试使用 Java 序列化来序列化 protobuf 时遇到的问题的看法(似乎其他人也遇到了同样 问题)。我在这里放置了一个示例堆栈跟踪。该错误一开始很令人困惑,但查看GenevedMessageLite中的相关行我想我看到了问题:https://github.com/google/protobuf/blob/774d630bde574f5fcbb6dae6eaa0f91f7bc12967/java/src/main/java/com/google/ protobuf/GenerateMessageLite.java#L768

对 的调用Class.forName不包含ClassLoader参数,因此它将尝试使用ClassLoader用于加载 的 任何一个GeneratedMessageLite。由于 Spark 本身使用 protobuf-java,因此这最终成为ClassLoader链的更上游,它对我们的 protobuf 类一无所知。为了确认这就是问题所在,我们在 Spark 执行器中运行了以下代码:

// fails with ClassNotFoundException
Class.forName("com.hubspot.Protos$ExampleProto", true, GeneratedMessageLite.class.getClassLoader());
// works fine
Class.forName("com.hubspot.Protos$ExampleProto", true, Thread.currentThread().getContextClassLoader());

Spark 案例中有一些解决方法,但这似乎是一个更普遍的问题,可能会在 Spark 之外出现。是否考虑切换 的所有用法以Class.forName包含显式ClassLoader参数?这样,可以优先考虑线程的上下文类加载器,类似于Guava 中的 Resources 类使用的策略

我很想听听您对这个问题的看法,谢谢

回答

4

我还应该注意,调用Class.forName是在方法内部,readResolve所以如果使用 Java 序列化/反序列化,这只是一个问题

6

感谢您的报告。所提出的解决方案对我来说听起来不错。您能帮忙发出拉取请求来修复它吗?

0

@jhaber 如果您不介意,可以重新发送拉取请求吗?

0

当然可以,重新发送拉取请求。

9

似乎可以从 获得正确的类加载器regularForm.getClass().getClassLoader()

5

regularForm在序列化端可用,用于初始化SerializedForm字段,但随后SerializedForm使用 Java 序列化进行序列化。因此,在反序列化时,在readResolve方法内部,我们拥有的唯一信息是序列化的字段(messageClassNameasBytes)。我们可以添加更多字段SerializedForm,但我认为没有任何东西可以帮助解决这个特定问题(无法序列化ClassLoader或任何东西)

6

哦,明白了,是连载。类是可序列化的,并且将其包含在这里确实有意义,因为它需要 Java 弄清楚如何处理它......如果 Java 可以重新构造该类,那么类加载器将是正确的。

看起来 Collections.checkedCollection 保留了用于序列化的类,并且具有显式序列化支持。

只是不清楚在某些环境中 Java 如何重新构建它......ObjectInputStream.resolveClass说它通过(null 表示“系统类加载器”;只是 jdk 类)选择类加载器:

如果当前线程堆栈上有一个方法,其声明类是由用户定义的类加载器定义的(并且不是为实现反射调用而生成的),则加载器是与当前执行帧最接近的此类方法对应的类加载器;否则,加载程序为空

不过,这似乎不适用于 RMI,因为我认为它会生成自己的线程来处理幕后,所以我猜 RMI 会覆盖resolveClass().是的。我越深入地研究这一点,“保存对”的引用看起来就越合适Class。另外,保存构建器的类而不是消息的类可能是个好主意,但这可能并不重要。

4

我发送了包含该更改的 PR,但我实际上不确定它会解决 Spark 问题。根据我对该 Javadoc 的阅读,我担心堆栈上最接近的方法将属于GeneratedMessageLite这种情况,我们又回到了开始的地方

2

@jhaber,解码时堆栈上最接近的方法,因此无论调用 ObjectInputStream。ObjectInputStream最终将创建SerializedForm并重构Class,然后调用readResolve().

1

由上述 PR 修复