Описание проблемы
У нас есть кластер 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, которая несовместима с нашей версией.
- Мы создаем JAR-файлы заданий Hadoop с помощью Apache Maven.
Возможные (и невозможные) подходы
(1) Переименование пакетов
Первый подход, который пришел нам в голову, состоял в том, чтобы переименовать пакеты в нашей собственной ветке Kryo, используя функции перемещения подключаемого модуля Maven Shade и выпустить его с другим идентификатором артефакта, чтобы мы могли полагаться на оба артефакта в нашей Конверсионный рабочий проект. Затем мы создаем экземпляр одного объекта Kryo как старой, так и новой версии и используем старый для десериализации, а новый — для повторной сериализации объекта.
Проблемы
Мы не используем Kryo явно в заданиях Hadoop, а получаем к нему доступ через несколько уровней наших собственных библиотек. Для каждой из этих библиотек необходимо
- переименовать вовлеченные пакеты и
- создать выпуск с другой группой или идентификатором артефакта
Чтобы еще больше запутать ситуацию, мы также используем сериализаторы Kryo, предоставляемые другими сторонними библиотеками, для которых нам придется делать то же самое.
(2) Использование нескольких загрузчиков классов
Второй подход, который мы придумали, заключался в том, чтобы вообще не зависеть от Kryo в проекте Maven, который содержит задание на преобразование, а загружать необходимые классы из 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. Это включает в себя несколько пользовательских сериализаторов, зарегистрированные идентификаторы классов и так далее. Например, мы не смогли найти способ установить пользовательские сериализаторы для классов без получения ошибки 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
ссылается на класс Serializer
Kryo и пытается загрузить его, используя свой собственный загрузчик классов (который является загрузчиком классов System), который не знает класс Serializer
.
(3) Использование промежуточной сериализации
В настоящее время наиболее многообещающим подходом кажется использование независимой промежуточной сериализации, например. JSON с использованием Gson или аналогичного, а затем запустить два отдельных задания:
- kryo:2.21.2-ourpatchbranch в нашем обычном хранилище -> JSON во временном хранилище
- JSON во временном хранилище -> kryo:2-22 в нашем обычном хранилище
Проблемы
Самая большая проблема с этим решением заключается в том, что оно примерно вдвое увеличивает объем обрабатываемых данных. Кроме того, нам нужен другой метод сериализации, который без проблем работает со всеми нашими данными, которые нам нужно будет изучить в первую очередь.
kyro.addDefaultSerializer(URI.class, URISerializer.class)
напрямую? Зачем использовать отражение? И какой класс вызываетNoClassDefFoundError
? - person skirsch   schedule 22.04.2013Kryo
не был известен во время компиляции. - person Michael Schmeißer   schedule 22.04.2013