处理序列化框架的不兼容版本更改问题描述问题简述相关事实概述可能(和不可能)的方法
问题描述
我们有一个Hadoop集群,我们在其上存储数据,这些数据使用Kryo(一个序列化框架)序列化为字节。我们用来执行此操作的Kryo版本已从官方版本2.21中分叉出来,以将我们自己的补丁应用于我们使用Kryo遇到的问题。当前的Kryo版本2.22也修复了这些问题,但解决方案不同。因此,我们不能仅仅更改我们使用的Kryo版本,因为这意味着我们将不再能够读取已经存储在Hadoop集群上的数据。为了解决这个问题,我们想要运行一个Hadoop作业,它
- 读取存储的数据
- 反序列化与旧版本的 Kryo 一起存储的数据
- 使用新版本的 Kryo 序列化还原的对象
- 将新的序列化表示形式写回我们的数据存储
问题在于,在一个Java程序中使用同一类的两个不同版本并非易事(更确切地说,在Hadoop作业的映射器类中)。
问题简述
如何在一个 Hadoop 作业中反序列化和序列化具有同一序列化框架的两个不同版本的对象?
相关事实概述
- 我们已将数据存储在Hadoop CDH4集群上,并使用Kryo版本2.21.2-ourpatch分支进行序列化
- 我们希望使用 Kryo 版本 2.22 序列化数据,这与我们的版本不兼容
- 我们使用Apache Maven构建Hadoop作业JAR
可能(和不可能)的方法
(1) 重命名软件包
我们想到的第一种方法是使用Maven Shade插件的重定位功能重命名我们自己的Kryo分支中的软件包,并使用不同的工件ID发布它,以便我们可以在转换作业项目中依赖这两个工件。然后,我们将实例化旧版本和新版本的一个 Kryo 对象,并使用旧对象进行反序列化,并使用新对象再次序列化该对象。
问题
我们没有在Hadoop作业中明确使用Kryo,而是通过我们自己的库的多层访问它。对于这些库中的每一个,有必要
- 重命名涉及的包和
- 创建具有不同组或项目 ID 的发布
为了使事情变得更加混乱,我们还使用其他第三方库提供的Kryo序列化程序,为此我们必须做同样的事情。
(2) 使用多个类装入器
我们提出的第二种方法是在包含转换作业的Maven项目中完全不依赖Kryo,而是从JAR加载每个版本的所需类,这些类存储在Hadoop的分布式缓存中。然后,序列化对象将如下所示:
public byte[] serialize(Object foo, JarClassLoader cl) {
final Class<?> kryoClass = cl.loadClass("com.esotericsoftware.kryo.Kryo");
Object k = kryoClass.getConstructor().newInstance();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
final Class<?> outputClass = cl.loadClass("com.esotericsoftware.kryo.io.Output");
Object output = outputClass.getConstructor(OutputStream.class).newInstance(baos);
Method writeObject = kryoClass.getMethod("writeObject", outputClass, Object.class);
writeObject.invoke(k, output, foo);
outputClass.getMethod("close").invoke(output);
baos.close();
byte[] bytes = baos.toByteArray();
return bytes;
}
问题
虽然这种方法可能适用于实例化未配置的 Kryo 对象并序列化/还原某些对象,但我们使用的 Kryo 配置要复杂得多。这包括多个自定义序列化程序、注册的类 ID 等。例如,我们无法找到一种方法来设置类的自定义序列化程序而不获取NoClassDefFoundError - 以下代码不起作用:
Class<?> kryoClass = this.loadClass("com.esotericsoftware.kryo.Kryo");
Object kryo = kryoClass.getConstructor().newInstance();
Method addDefaultSerializer = kryoClass.getMethod("addDefaultSerializer", Class.class, Class.class);
addDefaultSerializer.invoke(kryo, URI.class, URISerializer.class); // throws NoClassDefFoundError
最后一行抛出一个
java.lang.NoClassDefFoundError: com/esotericsoftware/kryo/Serializer
因为该类引用了 Kryo 的类,并尝试使用自己的类装入器(即 System 类装入器)装入它,而该类不知道该类。URISerializer
Serializer
Serializer
(3) 使用中间序列化
目前最有前途的方法似乎是使用独立的中间序列化,例如使用Gson或类似方式的JSON,然后运行两个单独的作业:
- kryo:2.21.2-我们的常规商店中的我们的补丁分支 ->临时商店中的JSON
- JSON 在临时商店 -> kryo:2-22 在我们的常规商店
问题 此解决方案的最大问题是,它大约使所处理数据的空间消耗增加了一倍。此外,我们需要另一种序列化方法,该方法可以在所有数据上没有问题,我们需要首先对其进行调查。