问题描述
我们有一个Hadoop集群,我们存储使用Kryo(序列化框架)将数据序列化为字节的数据.我们以前做过的Kryo版本已经从官方版本2.21中分离出来,将我们自己的补丁应用到使用Kryo所遇到的问题上.目前的Kryo版本2.22也解决了这些问题,但是使用了不同的解决方案.因此,我们不能仅仅更改我们使用的Kryo版本,因为这将意味着我们将无法再读取已经存储在Hadoop集群中的数据.为了解决这个问题,我们要运行一个Hadoop作业
>读取存储的数据
>反序列化旧版Kryo存储的数据
>使用新版本的Kryo序列化恢复的对象
>将新的序列化表示写回我们的数据存储
问题是在一个Java程序中使用同一个类的两个不同版本(更确切地说,在Hadoop作业的映射器类中)并不是微不足道的.
简单的问题
在一个Hadoop作业中,如何使用两个不同版本的同一序列化框架反序列化序列化对象?
相关事实概述
>我们将数据存储在Hadoop CDH4集群中,序列化为Kryo版本2.21.2-ourpatchbranch
>我们希望将数据序列化为Kryo版本2.22,这与我们的版本不兼容
>我们用Apache Maven构建我们的Hadoop工作JAR
可能(和不可能)的方法
(1)重命名软件包
我们想到的第一个方法是使用relocation functionality of the Maven Shade plugin重命名我们自己的Kryo分支中的软件包,并使用不同的工件ID进行发布,以便我们可以依赖于我们的转换作业项目中的两个工件.然后,我们将实例化旧版本和新版本的一个Kryo对象,并使用旧版本进行反序列化,并将新的对象重新序列化.
问题
我们不会在Hadoop作业中明确使用Kryo,而是通过我们自己的图书馆的多个层访问它.对于每个这些库,都是必要的
>重命名涉及包和
>创建具有不同组或工件ID的版本
为了使事情更加凌乱,我们还使用其他第三方库提供的Kryo序列化程序,我们必须做同样的事情.
(2)使用多个类装载器
我们想出的第二种方法是在包含转换作业的Maven项目中完全不依赖于Kryo,而是从存储在Hadoop分布式缓存中的每个版本的JAR加载所需的类.序列化一个对象然后将看起来像这样:
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配置.这包括几个自定义序列化程序,注册的类别等等.例如,我们无法找出一种方法来为类设置自定义序列化,而不会得到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
因为URISerializer类引用了Kryo的Serializer类,并尝试使用它自己的类加载器(它是System类加载器)加载它,它不知道Serializer类.
(3)使用中间序列化
目前最有希望的方法似乎是使用独立的中间序列化,例如JSON使用Gson或类似的,然后运行两个单独的作业:
> kryo:2.21.2-ourpatchbranch在我们的常规商店 – > JSON在临时商店
>临时商店中的JSON – > kryo:2-22在我们的常规商店
问题
这个解决方案最大的问题是它大大加倍了处理的数据的空间消耗.此外,我们需要另一个序列化方法,在我们所有的数据上都没有问题,我们首先需要进行调查.