Углубленная сериализация (2) — сериализация Kryo

задняя часть

В предыдущей статье мы представили два распространенных метода сериализации в Java, сериализацию JDK и сериализацию Hessian 2. В этой статье мы поговорим о восходящей звезде, сериализации Kryo, которая известна как самая быстрая структура сериализации в Java. Итак, без лишних слов, давайте посмотрим, на что способна эта многообещающая звезда.

Крио Сериализация

Kryo — это быстрый инструмент сериализации/десериализации, основанный на механизме генерации байт-кода (используется базовая библиотека ASM), поэтому он имеет определенные преимущества в скорости сериализации, но из-за этого его использование может быть ограничено только языками на основе JVM.

В Интернете много информации о том, что Kryo можно использовать только в Java, что неверно, ведь помимо Java языки на основе JVM, такие как Scala и Kotlin, также могут использовать Kryo для достижения сериализации.

Как и в случае с Hessian, результат, сериализованный Kryo, имеет собственный уникальный формат. Поскольку сериализованный результат является двоичным, то есть byte[], механизм хранения, такой как Redis, который может хранить двоичные данные, может напрямую хранить данные, сериализованные Kryo. Конечно, вы также можете преобразовать его в строку и сохранить в других механизмах хранения (потеря производительности).

Благодаря отличной производительности Kryo стал базовым протоколом сериализации для многих известных фреймворков Java, включая, помимо прочего, 👇

Официальный адрес сайта:GitHub.com/esoteric, так что F…

Основное использование

Рассказав так много, давайте взглянем на основы использования Kryo. По сути, для фреймворка сериализации API в основном одинаков, ведь входные и выходные параметры обычно детерминированы (объект, который нужно сериализовать/результат сериализации). Перед использованием Kryo нам нужно ввести соответствующие зависимости

<dependency>
  <groupId>com.esotericsoftware</groupId>
  <artifactId>kryo</artifactId>
  <version>5.2.0</version>
</dependency>

Основное использование выглядит следующим образом 👇

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import java.io.*;
​
public class HelloKryo {
   static public void main(String[] args) throws Exception {
       Kryo kryo = new Kryo();
       kryo.register(SomeClass.class);
​
       SomeClass object = new SomeClass();
       object.value = "Hello Kryo!";
​
       Output output = new Output(new FileOutputStream("file.bin"));
       kryo.writeObject(output, object);
       output.close();
​
       Input input = new Input(new FileInputStream("file.bin"));
       SomeClass object2 = kryo.readObject(input, SomeClass.class);
       input.close();
       System.out.println(object2.value);
  }
​
   static public class SomeClass {
       String value;
  }
}
​

Класс Kryo выполняет сериализацию автоматически. Классы Output и Input отвечают за обработку буферизованных байтов и их запись в поток.

Сериализация Крио

Будучи гибкой структурой сериализации, Kryo не заботится о чтении и записи данных.Как разработчик, вы можете свободно использовать стандартные сериализаторы, предоставляемые Kryo.

Регистрация Крио

Как и многие другие среды сериализации, Kryo предоставляет способ регистрации сериализованных классов объектов, чтобы обеспечить производительность и уменьшить размер сериализованных результатов. Во время регистрации для сериализованного класса создается идентификатор int, который используется для уникальной идентификации типа во время сериализации. Способ регистрации следующий:

kryo.register(SomeClass.class);

или

kryo.register(SomeClass.class, 1);

Вы можете явно указать регистрацию int ID и тому подобное, но ID должен быть больше или равен 0. Если нет, он будет использовать внутренний способ int ++ для поддержания упорядоченной генерации идентификатора int.

Сериализаторы для Kryo

Kryo поддерживает множество сериализаторов, один или два мы можем увидеть в исходном коде 👇

Подробности смотрите в 👉«Типы сериализации, поддерживаемые Kryo»

Хотя сериализаторы, предоставляемые Kryo, могут читать и записывать большинство объектов, разработчики могут легко создавать свои собственные сериализаторы. Из-за нехватки места я не буду здесь расширять описание, просто возьму в качестве примера сериализатор по умолчанию.

ссылка на объект

В новых версиях Kryo ссылки на объекты по умолчанию отключены. Это означает, что если объект появляется в графе объектов несколько раз, он будет записан несколько раз и будет десериализован в несколько разных объектов.

Например, когда включено свойство ссылки, при первом появлении каждого объекта в графе объектов записывается вариант, чтобы пометить его при записи. Когда тот же объект появляется позже, записывается только один вариант, чтобы достичь цели экономии места. Хотя это сэкономит место для сериализации, это способ обмена времени на пространство, что повлияет на производительность сериализации, поскольку при записи/чтении объектов требуется отслеживание.

Разработчики могут использовать собственныеsetReferencesметод, чтобы решить, следует ли включить функцию цитирования Kryo.

поток небезопасен

Kryo не является потокобезопасным. Каждый поток должен иметь свой экземпляр объекта Kryo, ввод и вывод.

Следовательно, в многопоточной среде рассмотрите возможность использования ThreadLocal или целевого пула для обеспечения безопасности потоков.

ThreadLocal + Kryo устраняет небезопасность потоков

ThreadLocal — это типичный способ пожертвовать пространством для обеспечения безопасности параллелизма, он создаст специфичный для потока объект kryo для каждого потока отдельно. Для каждого крио-объекта каждого потока он выполняется последовательно, что естественным образом позволяет избежать проблем с безопасностью параллелизма. Метод создания следующий:

static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
  protected Kryo initialValue() {
     Kryo kryo = new Kryo();
     // 在此处配置kryo对象的使用示例,如循环引用等
     return kryo;
  };
};
​
Kryo kryo = kryos.get();

После этого просто пройтиkryos.get()Этот метод можно использовать, выводя объект из контекста потока.

Пул объектов + Kryo для решения проблемы небезопасности потоков

** "Бассейн"Это очень важная идея программирования: пулы соединений, пулы потоков, пулы объектов и т. д.Воплощение идеи «повторного использования», путем сохранения созданного «объекта» в «контейнере» для последующего многократного использования, позволяет избежать потери производительности, вызванной созданием и уничтожением, с целью повышения общей производительности.

То же самое верно и для объединения объектов Kryo. Фреймворк Kryo поставляется с реализацией пула объектов, и весь процесс использования представляет собой не что иное, какСоздать пул, получить объект из пула, вернуть объектТри шага, ниже приведен пример кода.

// Pool constructor arguments: thread safe, soft references, maximum capacity
Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
  protected Kryo create () {
     Kryo kryo = new Kryo();
     // Kryo 配置
     return kryo;
  }
};
​
// 获取池中的Kryo对象
Kryo kryo = kryoPool.obtain();
// 将kryo对象归还到池中
kryoPool.free(kryo);

При создании пула Kryo вам необходимо передать три параметра.Первый параметр используется для указания, использовать ли синхронизацию внутри пула.Если он указан как true, разрешен одновременный доступ нескольких потоков. Третий параметр подходит для указания размера пула объектов, эти два параметра проще понять, поэтому сосредоточимся на втором параметре.

Если для второго параметра установлено значение true, пул Kryo будет использовать java.lang.ref.SoftReference для хранения объектов. Это позволяет объектам в пуле собирать мусор, когда нагрузка на память JVM высока. Очистка пула удаляет все программные ссылки на объекты, которые были удалены сборщиком мусора. Это может уменьшить размер пула, если не задана максимальная емкость. Когда пул имеет максимальную емкость, нет необходимости вызывать clean, потому что при достижении максимальной емкости Pool free попытается удалить нулевую ссылку.

После создания игрового пула Kryo пользоваться Kryo очень просто, достаточно позвонитьkryoPool.obtain()Метод может быть вызван после использованияkryoPool.free(kryo)Когда объект возвращается, полная аренда завершается.

Теоретически, если правильно оценить размер пула объектов, он может идеально решить проблему безопасности параллелизма, занимая очень мало места в памяти. Если вы хотите инкапсулировать метод сериализации Kryo, вы можете обратиться к следующему коду 👇

public static byte[] serialize(Object obj) {
   Kryo kryo = kryoPool.obtain();
   // 使用 Output 对象池会导致序列化重复的错误(getBuffer返回了Output对象的buffer引用)
   try (Output opt = new Output(1024, -1)) {
       kryo.writeClassAndObject(opt, obj);
       opt.flush();
       return opt.getBuffer();
  }finally {
       kryoPool.free(kryo);
  }
}

резюме

По сравнению с методом сериализации, который поставляется с JDK, Kryo имеет более высокую производительность, а поскольку Kryo допускает множественные ссылки и циклические ссылки, накладные расходы на хранение также меньше.

Однако, хотя Kryo имеет очень хорошую производительность, он теряет многие функции, такие как безопасность потоков, модификация полей сериализованных объектов и т. д. Хотя эти недостатки могут быть в определенной степени устранены за счет хорошей расширяемости Kryo, разработчикам по-прежнему трудно начать работу, но это не влияет на его статус в Java.

Kryo также имеет множество отличных функций, подробности см. 👉EsotericSoftware/kryo

Наконец, если вы считаете, что эта статья полезна для вас, не скупитесь на внимание и лайки. Читатели также могут оставить сообщение в области комментариев, чтобы вместе обсудить и добиться прогресса~