Kryo序列化

Kryo的注册

和很多其他的序列化框架一样,Kryo 为了提供性能和减小序列化结果体积,提供注册的序列化对象类的方式。在注册时,会为该序列化类生成 int ID,后续在序列化时使用 int ID 唯一标识该类型。注册的方式如下:

1
kryo.register(SomeClass.class);

线程不安全

Kryo 不是线程安全的。每个线程都应该有自己的 Kryo 对象、输入和输出实例。因此在多线程环境中,可以考虑使用 ThreadLocal 或者对象池来保证线程安全性。

ThreadLocal 是一种典型的牺牲空间来换取并发安全的方式,它会为每个线程都单独创建本线程专用的 kryo 对象。对于每条线程的每个 kryo 对象来说,都是顺序执行的,因此天然避免了并发安全问题。创建方法如下:

1
2
3
4
5
6
7
8
9
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相关配置参数详解

深入理解RPC之序列化篇–Kryo - 知乎 (zhihu.com)

1
2
kryo.setRegistrationRequired(false);//关闭注册行为
kryo.setReferences(true);//支持循环引用

Kryo支持注册行为,如kryo.register(SomeClazz.class);,这会赋予该Class一个从0开始的编号。但Kryo使用注册行为最大的问题在于,其不保证同一个Class每一次注册的号码相同,这与注册的顺序有关,也就意味着在不同的机器、同一个机器重启前后都有可能拥有不同的编号,这会导致序列化产生问题,所以在分布式项目中,一般关闭注册行为。

第二个注意点在于循环引用,Kryo为了追求高性能,可以关闭循环引用的支持。不过我并不认为关闭它是一件好的选择,大多数情况下,请保持kryo.setReferences(true)

Kryo默认情况下是不启用对象引用的。这意味着如果一个对象多次出现在一个对象图中,它将被多次写入,并将被反序列化为多个不同的对象。

举个例子,当开启了引用属性,每个对象第一次出现在对象图中,会在记录时写入一个 varint,用于标记。当此后有同一对象出现时,只会记录一个 varint,以此达到节省空间的目标。此举虽然会节省序列化空间,但是是一种用时间换空间的做法,会影响序列化的性能,这是因为在写入/读取对象时都需要进行追踪。

开发者可以使用 kryo 自带的 setReferences 方法来决定是否启用 Kryo 的引用功能。

深入浅出序列化(2)——Kryo序列化